Compare commits

45 Commits

Author SHA1 Message Date
892e282d1d Real Debug to event Participant status 2025-07-19 12:09:09 +02:00
adc46114d8 Debug to event Participant status + Fix No Participant in History 2025-07-19 11:51:48 +02:00
c88718fe35 Modified InitPursuit to get Rankings ONLY FROM DATAASSET 2025-07-18 23:20:34 +02:00
8fe2750a54 RemoteSubsystem update 2025-07-18 17:36:17 +02:00
33d3ea1e37 FixSortSplitRankingsByRank Function Parameter Name 2025-07-18 02:32:15 +02:00
0bc765cd29 Fix Output BluePrint V2 2025-07-18 02:22:40 +02:00
dbe904e388 Fix Output BluePrint ByRef 2025-07-18 02:05:34 +02:00
1f35fcbcf5 Fix Compile Error 2025-07-18 01:54:28 +02:00
65c32d9240 Bug Fix Type Error On next code iteration 2025-07-18 01:11:44 +02:00
609f623737 Modified Function Sort Split Ranking 2025-07-18 01:10:00 +02:00
e02ed8538f Added Sort Ranking 2025-07-17 19:21:32 +02:00
9b85bfc94a Fixing Compile Error 2025-07-17 19:07:50 +02:00
da89e35eb2 Added Module Remote To add HTTP basic RemoteControl 2025-07-16 02:41:08 +02:00
d6b8874827 Test Synchro 2 2025-07-15 19:55:44 +02:00
193d8d4094 Test Synchro 2025-07-15 19:35:29 +02:00
3291eb366d Fixed SplitSensor Event made BlueprintAssignable 2025-07-15 16:51:26 +02:00
714e616fb1 Real Commit 2025-07-15 14:28:17 +02:00
17368f359f Fix LogMessage not in the right place + Protection Array dans GetSplitSensorType() 2025-07-15 11:07:21 +02:00
880ca9a3b1 Added Tracking Mechanism for Participant 2025-07-15 07:59:45 +02:00
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
49 changed files with 3019 additions and 1098 deletions

View File

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

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)
{ {

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,20 @@ UDTFluxModelAsset::UDTFluxModelAsset(const FObjectInitializer& ObjectInitializer
void UDTFluxModelAsset::AddContest(const FDTFluxContest& Contest) void UDTFluxModelAsset::AddContest(const FDTFluxContest& Contest)
{ {
Contests.Add(Contest.Name, Contest); Contests.Add(Contest.Name, Contest);
// initialisation
for (const auto& Stage : Contest.Stages)
{
FinishedStagesCache.Add(FDTFluxStageKey(Contest.ContestId, Stage.StageId), Stage.IsFinished());
}
TArray<FDTFluxSplit> Splits = Contest.Splits;
Splits.Sort([](const FDTFluxSplit& A, const FDTFluxSplit& B)
{
return A.SplitId < B.SplitId;
});
// last and Penultimate split cache for contest
LastSplitIdCache.Add(Contest.ContestId, Splits.Pop().SplitId);
PenultimateSplitIdCache.Add(Contest.ContestId, Splits.Pop().SplitId);
} }
bool UDTFluxModelAsset::GetContestById(const int InContestId, FDTFluxContest& OutContest) bool UDTFluxModelAsset::GetContestById(const int InContestId, FDTFluxContest& OutContest)
@ -132,6 +146,38 @@ 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;
}
void UDTFluxModelAsset::CacheSplitSensorInfo(const FDTFluxSplitSensorKey SplitSensorKey,
const FDTFluxSplitSensorInfo& SplitSensorInfo)
{
SplitSensorInfoCache.Add(SplitSensorKey, SplitSensorInfo);
}
bool UDTFluxModelAsset::CheckStageIsFinished(FDTFluxStageKey StageKey)
{
FDTFluxStage Stage;
if (GetStage(StageKey, Stage))
{
FinishedStagesCache.Add(StageKey, Stage.IsFinished());
return FinishedStagesCache[StageKey];
}
return false;
}
void UDTFluxModelAsset::UpdateOrCreateStageRanking(const FDTFluxStageRankings& InStageRankings) void UDTFluxModelAsset::UpdateOrCreateStageRanking(const FDTFluxStageRankings& InStageRankings)
{ {

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; FDTFluxPerson Person;
Person.FirstName = FirstName; Person.FirstName = FirstName.TrimStartAndEnd();
Person.LastName = LastName; Person.LastName = LastName.TrimStartAndEnd();
Person.Gender = Gender; Person.Gender = Gender.TrimStartAndEnd();
Teammate.Add(Person);
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, Error, TEXT("Ctor with JSON Object Teammate is now %i long"), Teammate.Num());
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,15 +35,24 @@ public:
UPROPERTY(BlueprintReadOnly, EditAnywhere) UPROPERTY(BlueprintReadOnly, EditAnywhere)
TMap<FString /* ContestName */, FDTFluxContest> Contests; TMap<FString /* ContestName */, FDTFluxContest> Contests;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) UPROPERTY(BlueprintReadOnly, EditAnywhere)
TMap<int /*ContestId*/, FDTFluxContestRankings> ContestRankings; TMap<int /*ContestId*/, FDTFluxContestRankings> ContestRankings;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) UPROPERTY(BlueprintReadOnly, EditAnywhere)
TMap<FDTFluxStageKey, FDTFluxStageRankings> StageRankings; TMap<FDTFluxStageKey, FDTFluxStageRankings> StageRankings;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) UPROPERTY(BlueprintReadOnly, EditAnywhere)
TMap<FDTFluxSplitKey, FDTFluxSplitRankings> SplitRankings; TMap<FDTFluxSplitKey, FDTFluxSplitRankings> SplitRankings;
UPROPERTY(BlueprintReadOnly, EditAnywhere)
TMap<FDTFluxSplitSensorKey, FDTFluxSplitSensorInfo> SplitSensorInfoCache;
UPROPERTY(BlueprintReadOnly, EditAnywhere)
TMap<int /*ContestId*/, int /*SplitId*/> LastSplitIdCache;
UPROPERTY(BlueprintReadOnly, EditAnywhere)
TMap<int/*ContestId*/, int /*Penultimate*/>PenultimateSplitIdCache;
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|ModelAsset") UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|ModelAsset")
void AddContest(const FDTFluxContest& Contest); void AddContest(const FDTFluxContest& Contest);
@ -89,4 +98,15 @@ 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);
UFUNCTION()
void CacheSplitSensorInfo(const FDTFluxSplitSensorKey SplitSensorKey, const FDTFluxSplitSensorInfo& SplitSensorInfo);
private:
UPROPERTY()
TMap<FDTFluxStageKey, bool /*bIsFinished*/> FinishedStagesCache;
UFUNCTION()
bool CheckStageIsFinished(FDTFluxStageKey StageKey);
}; };

View File

@ -11,79 +11,80 @@
UENUM(BlueprintType, Category="DTFlux|Model") UENUM(BlueprintType, Category="DTFlux|Model")
enum class EDTFluxParticipantStatusType : uint8 enum class EDTFluxParticipantStatusType : uint8
{ {
Normal = 0 UMETA(DisplayName="Normal"), Normal = 0 UMETA(DisplayName="Normal"),
OutOfRace = 1 UMETA(DisplayName="HorsCourse"), OutOfRace = 1 UMETA(DisplayName="HorsCourse"),
DSQ = 2 UMETA(DisplayName="Disqualifié"), DSQ = 2 UMETA(DisplayName="Disqualifié"),
DNF = 3 UMETA(DisplayName="Abandon"), DNF = 3 UMETA(DisplayName="Abandon"),
DNS = 4 UMETA(DisplayName="NonPartant"), DNS = 4 UMETA(DisplayName="NonPartant"),
NotLinedUp = 5 UMETA(DisplayName="NonPresentAuDépart"), NotLinedUp = 5 UMETA(DisplayName="NonPresentAuDépart"),
Unknown = 1 << 4 UMETA(DisplayName="Unknown") Unknown = 1 << 4 UMETA(DisplayName="Unknown")
}; };
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"),
Finish = 0b0000001 UMETA(DisplayName="Finish"), Finish = 0b0000001 UMETA(DisplayName="Finish"),
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
{ {
None = 0b00000000 UMETA(DisplayName="Undefined"), None = 0b00000000 UMETA(DisplayName="Undefined"),
PreFinnish = 0b00000001 UMETA(DisplayName="PreFinnishSplit"), PreFinnish = 0b00000001 UMETA(DisplayName="PreFinnishSplit"),
Finish = 0b00000010 UMETA(DisplayName="FinishSplit"), Finish = 0b00000010 UMETA(DisplayName="FinishSplit"),
Regular = 0b00000100 UMETA(DisplayName="Regular"), Regular = 0b00000100 UMETA(DisplayName="Regular"),
}; };
ENUM_CLASS_FLAGS(EDTFluxSplitType); ENUM_CLASS_FLAGS(EDTFluxSplitType);
UENUM(BlueprintType, meta=(Bitflags, UseEnumValuesAsMaskValuesInEditor=true)) UENUM(BlueprintType, meta=(Bitflags, UseEnumValuesAsMaskValuesInEditor=true))
enum class EDTFluxSortingFilter : uint8 enum class EDTFluxSortingFilter : uint8
{ {
None = 0b00000000 UMETA(DisplayName="No Sorting"), None = 0b00000000 UMETA(DisplayName="No Sorting"),
IgnoreStatusOut = 0b00000001 UMETA(DisplayName="IgnoreStatusOut"), IgnoreStatusOut = 0b00000001 UMETA(DisplayName="IgnoreStatusOut"),
Ascending = 0b00000010 UMETA(DisplayName="Ascending"), Ascending = 0b00000010 UMETA(DisplayName="Ascending"),
Descending = 0b00000100 UMETA(DisplayName="Descending"), Descending = 0b00000100 UMETA(DisplayName="Descending"),
IgnoreEmpty = 0b00001000 UMETA(DisplayName="IgnoreEmpty"), IgnoreEmpty = 0b00001000 UMETA(DisplayName="IgnoreEmpty"),
ByRank = 0b00010000 UMETA(DisplayName="ByRank"), ByRank = 0b00010000 UMETA(DisplayName="ByRank"),
ByAlpha = 0b01000000 UMETA(DisplayName="ByAlpha"), ByAlpha = 0b01000000 UMETA(DisplayName="ByAlpha"),
ByStartTime = 0b00100000 UMETA(DisplayName="ByStartTime"), ByStartTime = 0b00100000 UMETA(DisplayName="ByStartTime"),
AscendingByRank = Ascending | ByRank UMETA(DisplayName="AscendingByRank"), AscendingByRank = Ascending | ByRank UMETA(DisplayName="AscendingByRank"),
DescendingByRank= Descending | ByRank UMETA(DisplayName="DescendingByRank") DescendingByRank = Descending | ByRank UMETA(DisplayName="DescendingByRank")
}; };
ENUM_CLASS_FLAGS(EDTFluxSortingFilter); ENUM_CLASS_FLAGS(EDTFluxSortingFilter);
UENUM(BlueprintType) UENUM(BlueprintType)
enum class EDTFluxSortingRankingType: uint8 enum class EDTFluxSortingRankingType: uint8
{ {
Rank = 0b00000000 UMETA(DisplayName="Rank (Default)"), Rank = 0b00000000 UMETA(DisplayName="Rank (Default)"),
Name = 0b00000001 UMETA(DisplayName="Name"), Name = 0b00000001 UMETA(DisplayName="Name"),
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"),
RunningSpeed = 0b01000000 UMETA(DisplayName="StartTime"), RunningSpeed = 0b01000000 UMETA(DisplayName="StartTime"),
TotalSpeed = 0b10000000 UMETA(DisplayName="StartTime"), TotalSpeed = 0b10000000 UMETA(DisplayName="StartTime"),
}; };

View File

@ -109,3 +109,61 @@ struct DTFLUXCORE_API FDTFluxSplitKey : public FDTFluxCompositeKey
); );
} }
}; };
USTRUCT(BlueprintType)
struct DTFLUXCORE_API FDTFluxSplitSensorKey : public FDTFluxCompositeKey
{
GENERATED_BODY()
public:
FDTFluxSplitSensorKey() = default;
FDTFluxSplitSensorKey(const int InContestId, const int InStageId, const int InSplitId, const int InBib) :
ContestId(InContestId),
StageId(InStageId),
SplitId(InSplitId),
Bib(InBib){};
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
int ContestId = 0;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
int StageId = 0;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
int SplitId = 0;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
int Bib = 0;
friend uint32 GetTypeHash(const FDTFluxSplitSensorKey& Key)
{
return HashCombine(
GetTypeHash(Key.ContestId),
GetTypeHash(Key.StageId),
GetTypeHash(Key.SplitId),
GetTypeHash(Key.Bib)
);
}
bool operator==(const FDTFluxSplitSensorKey& Other) const
{
return ContestId == Other.ContestId && StageId == Other.StageId
&& SplitId == Other.SplitId && Bib == Other.Bib;
}
FString GetDisplayName() const
{
return FString::Printf(TEXT("Contest%i | Stage%i | Split%i | Bib%i"), ContestId, StageId, SplitId, Bib);
}
FText GetTooltipText() const
{
return FText::Format(INVTEXT("Contest{0}|Stage{1}|Split{2}"),
FText::AsNumber(ContestId),
FText::AsNumber(StageId),
FText::AsNumber(SplitId),
FText::AsNumber(Bib)
);
}
};

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

@ -17,7 +17,17 @@ struct FDTFluxSplitSensorInfo
public: public:
FDTFluxSplitSensorInfo() = default; FDTFluxSplitSensorInfo() = default;
FDTFluxSplitSensorInfo(const FString InSplitName):
Bib(-1),
ContestId(-1),
StageId(-1),
SplitId(-1),
Time(""),
Gap("-"),
Rank(-1),
SplitName(InSplitName)
{
};
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
int Bib = -1; int Bib = -1;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
@ -31,7 +41,23 @@ public:
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
FString Gap = "-"; FString Gap = "-";
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
int Rank; int Rank = -1;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
FString SplitName = "";
}; };
USTRUCT(BlueprintType)
struct FDTFluxSplitHistory
{
GENERATED_BODY()
public:
FDTFluxSplitHistory() = default;
UPROPERTY(BlueprintReadOnly, EditAnywhere)
FDTFluxParticipant Participant = FDTFluxParticipant();
UPROPERTY(BlueprintReadOnly, EditAnywhere)
TArray<FDTFluxSplitSensorInfo> SplitSensors = TArray<FDTFluxSplitSensorInfo>();
};

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

@ -62,6 +62,7 @@ void UDTFluxCoreSubsystem::ProcessTrackedResponse(FDTFluxServerResponse& InRespo
FDTFluxContestRankings Rankings; FDTFluxContestRankings Rankings;
if (InResponse.ParseContestRanking(Rankings)) if (InResponse.ParseContestRanking(Rankings))
{ {
OnContestRankings.Broadcast(Rankings.ContestId, Rankings);
ProcessContestRanking(Rankings); ProcessContestRanking(Rankings);
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRanking added for Contest %s"), UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRanking added for Contest %s"),
*Rankings.ContestName); *Rankings.ContestName);
@ -77,6 +78,8 @@ void UDTFluxCoreSubsystem::ProcessTrackedResponse(FDTFluxServerResponse& InRespo
FDTFluxStageRankings Rankings; FDTFluxStageRankings Rankings;
if (InResponse.ParseStageRanking(Rankings)) if (InResponse.ParseStageRanking(Rankings))
{ {
FDTFluxStageKey StageKey(Rankings.ContestId, Rankings.StageId);
OnStageRankings.Broadcast(StageKey, Rankings);
ProcessStageRanking(Rankings); ProcessStageRanking(Rankings);
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRanking added for Stage %i of Contest %i"), UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRanking added for Stage %i of Contest %i"),
Rankings.StageId, Rankings.ContestId); Rankings.StageId, Rankings.ContestId);
@ -92,6 +95,8 @@ void UDTFluxCoreSubsystem::ProcessTrackedResponse(FDTFluxServerResponse& InRespo
FDTFluxSplitRankings Rankings; FDTFluxSplitRankings Rankings;
if (InResponse.ParseSplitRanking(Rankings)) if (InResponse.ParseSplitRanking(Rankings))
{ {
FDTFluxSplitKey SplitKey(Rankings.ContestId, Rankings.StageId, Rankings.SplitId);
OnSplitRankings.Broadcast(SplitKey, Rankings);
ProcessSplitRanking(Rankings); ProcessSplitRanking(Rankings);
UE_LOG(logDTFluxCoreSubsystem, Warning, UE_LOG(logDTFluxCoreSubsystem, Warning,
TEXT("ContestRanking added for Split %i of Stage %i of Contest %i"), TEXT("ContestRanking added for Split %i of Stage %i of Contest %i"),
@ -180,12 +185,77 @@ 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;
}
EDTFluxFinisherType UDTFluxCoreSubsystem::GetSplitSensorType(const FDTFluxSplitSensorInfo& SplitSensorInfo)
{
if (DataStorage != nullptr)
{
if (DataStorage->LastSplitIdCache.Contains(SplitSensorInfo.ContestId))
{
int LastSplitIdForContest = DataStorage->LastSplitIdCache[SplitSensorInfo.ContestId];
if (LastSplitIdForContest == SplitSensorInfo.SplitId)
{
if (SplitSensorInfo.Rank == 1 )
{
return EDTFluxFinisherType::Winner;
}
return EDTFluxFinisherType::Finish;
}
return EDTFluxFinisherType::None;
}
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("LastSplitIdCache not found for ContestId %i"), SplitSensorInfo.ContestId);
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage not available"));
return EDTFluxFinisherType::None;
}
void UDTFluxCoreSubsystem::ProcessRaceData(const FDTFluxRaceData& RaceDataDefinition) void UDTFluxCoreSubsystem::ProcessRaceData(const FDTFluxRaceData& RaceDataDefinition)
{ {
if (RaceDataDefinition.Datas.Num() > 0) if (RaceDataDefinition.Datas.Num() > 0)
{ {
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Receiving RaceDataDefinition [%s]"), // UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Receiving RaceDataDefinition [%s]"),
*RaceDataDefinition.Datas[0].Name); // *RaceDataDefinition.Datas[0].Name);
if (DataStorage != nullptr) if (DataStorage != nullptr)
{ {
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage Name %s"), *DataStorage->EventName); UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage Name %s"), *DataStorage->EventName);
@ -262,20 +332,51 @@ void UDTFluxCoreSubsystem::ProcessTeamUpdate(const FDTFluxTeamListDefinition& Te
void UDTFluxCoreSubsystem::ProcessSplitSensor(const FDTFluxSplitSensorInfo& SplitSensorInfo) void UDTFluxCoreSubsystem::ProcessSplitSensor(const FDTFluxSplitSensorInfo& SplitSensorInfo)
{ {
FDTFluxContest Contest;
FDTFluxStageKey StageKey(SplitSensorInfo.ContestId, SplitSensorInfo.StageId);
FDTFluxStage Stage;
DataStorage->GetStage(StageKey, Stage);
FDTFluxParticipant Participant;
DataStorage->GetParticipantByBib(SplitSensorInfo.Bib, Participant);
DataStorage->GetContestById(SplitSensorInfo.ContestId, Contest); if (DataStorage != nullptr)
UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("%s|%s Split %i Sensor for Participant [Bib] %i [FullName] %s"), {
*Contest.Name, *Stage.Name, FString DebugString = FString::Printf(TEXT("Received SplitSensorInfo for Bib %i"), SplitSensorInfo.Bib);
SplitSensorInfo.SplitId, SplitSensorInfo.Bib, *Participant.GetFormattedName()); DebugString += FString::Printf(TEXT("ContestId[%i] StageId[%i] SplitId[%i] Time[%s], Gap[%s] Rank[%i]"),
SplitSensorInfo.ContestId, SplitSensorInfo.StageId, SplitSensorInfo.SplitId, *SplitSensorInfo.Time,
*SplitSensorInfo.Gap, SplitSensorInfo.Rank);
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received SplitSensorInfo :\n%s"), *DebugString)
// Gestion Cache Split Sensor
FDTFluxSplitSensorKey SplitSensorKey(SplitSensorInfo.ContestId, SplitSensorInfo.StageId, SplitSensorInfo.SplitId, -1);
FDTFluxSplitSensorInfo NewSplitSensorInfo = SplitSensorInfo;
if (DataStorage->SplitSensorInfoCache.Contains(SplitSensorKey))
{
NewSplitSensorInfo.SplitName = DataStorage->SplitSensorInfoCache[SplitSensorKey].SplitName;
}
SplitSensorKey.Bib = SplitSensorInfo.Bib;
DataStorage->SplitSensorInfoCache.Add(SplitSensorKey, NewSplitSensorInfo);
// Update Current currentSplit
FDTFluxParticipant Participant;
if (DataStorage->Participants.Contains(SplitSensorInfo.Bib))
{
DataStorage->Participants[SplitSensorInfo.Bib].CurrentSplit = SplitSensorInfo.SplitId;
}
// Gestion Finnish Status
switch (GetSplitSensorType(SplitSensorInfo))
{
case EDTFluxFinisherType::Winner:
{
OnWinner.Broadcast(SplitSensorInfo);
break;
}
case EDTFluxFinisherType::Finish :
{
OnFinisher.Broadcast(SplitSensorInfo);
break;
}
default:
{
OnSplitSensor.Broadcast(SplitSensorInfo);
break;
}
}
}
} }
void UDTFluxCoreSubsystem::SendRequest(const FString& Message) void UDTFluxCoreSubsystem::SendRequest(const FString& Message)
{ {
if (NetworkSubsystem) if (NetworkSubsystem)
@ -284,6 +385,232 @@ void UDTFluxCoreSubsystem::SendRequest(const FString& Message)
} }
} }
void UDTFluxCoreSubsystem::InitParticipantTracking(const int Bib, const int ContestId, const int StageId)
{
FDTFluxContest Contest;
if (GetContestForId(ContestId, Contest))
{
// get all splits
TArray<FDTFluxSplitSensorInfo> SplitSensorInfos;
FDTFluxSplitSensorKey SplitSensorKey;
for (auto Split : Contest.Splits)
{
SplitSensorKey = FDTFluxSplitSensorKey();
SplitSensorKey.ContestId = ContestId;
SplitSensorKey.StageId = StageId;
SplitSensorKey.Bib = Bib;
SplitSensorKey.SplitId = Split.SplitId;
if (DataStorage->SplitSensorInfoCache.Contains(SplitSensorKey))
{
FDTFluxSplitSensorInfo SplitSensorInfoToAdd = DataStorage->SplitSensorInfoCache[SplitSensorKey];
SplitSensorInfos.Add(SplitSensorInfoToAdd);
FString DebugString = FString::Printf(TEXT("SplitSensorInfo for Bib %i "), Bib);
DebugString += FString::Printf(TEXT("SplitSensorInfo [Rank] %i "), SplitSensorInfoToAdd.Rank);
DebugString += FString::Printf(TEXT("SplitSensorInfo [Rank] %s "), *SplitSensorInfoToAdd.Gap);
DebugString += FString::Printf(TEXT("SplitSensorInfo [Rank] %s "), *SplitSensorInfoToAdd.Time);
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("SplitSensorInfoCache contains SplitSensorInfo for Bib %i\nData : %s"), Bib, *DebugString);
}
else
{
SplitSensorInfos.Add(FDTFluxSplitSensorInfo(Split.Name));
}
}
FDTFluxSplitHistory History;
History.SplitSensors = SplitSensorInfos;
if (GetParticipant(Bib, History.Participant))
{
OnParticipantTrackingReady.Broadcast(History);
}
}
}
FGuid UDTFluxCoreSubsystem::InitContestRankingsDisplay(const int ContestId)
{
if (NetworkSubsystem)
{
if (DataStorage)
{
// no need to request ContestRankings;
if (IsContestRankingSealed(ContestId))
{
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRankings already Sealed for ContestId %i"), 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())
{
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Request IsSet()"));
TSharedPtr<FDTFluxServerResponse> ResponsePtr = Request.ParsedResponse.GetValue();
ResponsePtr->ParseContestRanking(Rankings);
this->DataStorage->AddContestRanking(Rankings);
this->OnContestRankingDisplayReady.Broadcast(Request.RequestId, true);
return;
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Request IsSet(FALSE)"));
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();
}
FGuid UDTFluxCoreSubsystem::InitStageRankingsDisplay(const int ContestId, const int StageId)
{
if (NetworkSubsystem)
{
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;
}
FGuid UDTFluxCoreSubsystem::InitSplitRankingsDisplay(const int ContestId, const int StageId, const int SplitId)
{
if (NetworkSubsystem)
{
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();
}
bool UDTFluxCoreSubsystem::GetStageRankingForBib(const int ContestId, const int StageId, const int Bib,
FDTFluxStageRanking& OutStageRanking)
{
if (DataStorage)
{
FDTFluxStageKey StageKey(ContestId, StageId);
if (DataStorage->StageRankings.Contains(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("Unable to find StageRanking for Bib %i"), Bib);
return false;
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage not available"));
return false;
}
bool UDTFluxCoreSubsystem::GetSplitRankingForBib(const int ContestId, const int StageId, const int SplitId,
const int Bib, FDTFluxSplitRanking& OutSplitRankings)
{
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;
}
bool UDTFluxCoreSubsystem::GetContestRankings(const int ContestId, bool UDTFluxCoreSubsystem::GetContestRankings(const int ContestId,
FDTFluxContestRankings& OutContestRankings) FDTFluxContestRankings& OutContestRankings)
{ {
@ -294,8 +621,10 @@ bool UDTFluxCoreSubsystem::GetContestRankings(const int ContestId,
} }
if (NetworkSubsystem) if (NetworkSubsystem)
{ {
TArray<int> TackedContestIds = {ContestId}; UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Requesting ContestRankings for ContestId %i"), ContestId);
TrackedRequestContestRankings(TackedContestIds); TArray<int> TrackedContestIds;
TrackedContestIds.Add(ContestId);
TrackedRequestContestRankings(TrackedContestIds);
return false; return false;
} }
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("NetworkSubsystem unavailable")); UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("NetworkSubsystem unavailable"));
@ -360,7 +689,7 @@ bool UDTFluxCoreSubsystem::GetSplitRankingsWithKey(const FDTFluxSplitKey SplitKe
} }
} }
TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestContestRankings(const TArray<int> ForContests) TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestContestRankings(const TArray<int> ForContests, bool bEnableCache)
{ {
if (NetworkSubsystem) if (NetworkSubsystem)
{ {
@ -386,7 +715,7 @@ TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestContestRankings(const TArray<i
for (auto ContestId : ForContests) for (auto ContestId : ForContests)
{ {
FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::ContestRanking, FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::ContestRanking,
ContestId, -1, -1, OnSuccess, OnError); ContestId, -1, -1, OnSuccess, OnError, bEnableCache);
RequestIds.Add(ContestRequest); RequestIds.Add(ContestRequest);
} }
return RequestIds; return RequestIds;
@ -394,7 +723,8 @@ TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestContestRankings(const TArray<i
return TArray<FGuid>(); return TArray<FGuid>();
} }
TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestStageRankings(const TArray<FDTFluxStageKey> ForStages) TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestStageRankings(const TArray<FDTFluxStageKey> ForStages,
bool bEnableCache)
{ {
if (NetworkSubsystem) if (NetworkSubsystem)
{ {
@ -416,11 +746,10 @@ TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestStageRankings(const TArray<FDT
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("StageRanking Request [%s] Error %s"), UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("StageRanking Request [%s] Error %s"),
*InReq.RequestId.ToString(), *InError); *InReq.RequestId.ToString(), *InError);
}); });
// if Contest is not ended
for (auto StageKey : ForStages) for (auto StageKey : ForStages)
{ {
FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::StageRanking, FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::StageRanking,
StageKey.ContestId, StageKey.StageId, -1, OnSuccess, OnError); StageKey.ContestId, StageKey.StageId, -1, OnSuccess, OnError, bEnableCache);
RequestIds.Add(ContestRequest); RequestIds.Add(ContestRequest);
} }
return RequestIds; return RequestIds;
@ -428,7 +757,8 @@ TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestStageRankings(const TArray<FDT
return TArray<FGuid>(); return TArray<FGuid>();
} }
TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestSplitRankings(const TArray<FDTFluxSplitKey> ForSplits) TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestSplitRankings(const TArray<FDTFluxSplitKey> ForSplits,
bool bEnableCache)
{ {
if (NetworkSubsystem) if (NetworkSubsystem)
{ {
@ -454,7 +784,7 @@ TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestSplitRankings(const TArray<FDT
for (auto SplitKey : ForSplits) for (auto SplitKey : ForSplits)
{ {
FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::SplitRanking, FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::SplitRanking,
SplitKey.ContestId, SplitKey.StageId, SplitKey.SplitId, OnSuccess, OnError); SplitKey.ContestId, SplitKey.StageId, SplitKey.SplitId, OnSuccess, OnError, bEnableCache);
RequestIds.Add(ContestRequest); RequestIds.Add(ContestRequest);
} }
return RequestIds; return RequestIds;
@ -462,13 +792,15 @@ TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestSplitRankings(const TArray<FDT
return TArray<FGuid>(); return TArray<FGuid>();
} }
const FDTFluxParticipant UDTFluxCoreSubsystem::GetParticipant(int InBib) 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();
return false;
} }
TArray<int> UDTFluxCoreSubsystem::GetCurrentContestsId() TArray<int> UDTFluxCoreSubsystem::GetCurrentContestsId()
@ -476,24 +808,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) const 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;
} }
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("No Contest running for Time [%s]"), *Time.ToString());
return TArray<int>();
} }
return Contests; 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)
@ -510,32 +848,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;
for (const auto& Pair : DataStorage->Contests)
{
FDTFluxContest Contest = Pair.Value;
int ContestId = Contest.ContestId;
if (Contest.Date < Time && Contest.EndTime > Time)
{
Contests.Add(Contest);
}
}
return Contests;
}
void UDTFluxCoreSubsystem::RequestRankingsForStages(TArray<FDTFluxStage> RequestedStages) const
{
}
TArray<FDTFluxContest> UDTFluxCoreSubsystem::GetContests()
{ {
if (DataStorage) if (DataStorage)
{ {
TArray<FDTFluxContest> OutContests; OutContests.Empty();
DataStorage->Contests.GenerateValueArray(OutContests); for (const auto& Pair : DataStorage->Contests)
return OutContests; {
FDTFluxContest Contest = Pair.Value;
int ContestId = Contest.ContestId;
//ils ont commencé.
if (Contest.Date < Time && Contest.EndTime > Time)
{
// ils sont finis
if (!Contest.IsFinished())
{
OutContests.Add(Contest);
}
}
}
if (!OutContests.IsEmpty())
{
return true;
}
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("No Contest running for Time [%s]"), *Time.ToString());
} }
return TArray<FDTFluxContest>(); UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage not available"));
return false;
}
bool UDTFluxCoreSubsystem::GetContests(TArray<FDTFluxContest>& OutContests)
{
OutContests.Empty();
if (DataStorage)
{
DataStorage->Contests.GenerateValueArray(OutContests);
return !OutContests.IsEmpty();
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage not available"));
return false;
}
void UDTFluxCoreSubsystem::GetContest(const int ContestId, FDTFluxContest& OutContest)
{
OutContest = FDTFluxContest();
if (GetContestForId(ContestId, OutContest))
{
return;
}
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

@ -7,6 +7,7 @@
#include "DTFluxCoreSubsystem.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)
@ -27,17 +28,19 @@ void UDTFluxPursuitManager::InitPursuit(const TArray<int> InContestIds, const in
FDTFluxContest Contest; FDTFluxContest Contest;
if (CoreSubsystem->GetContestForId(ContestId, Contest)) if (CoreSubsystem->GetContestForId(ContestId, Contest))
{ {
BindRankings(); // BindRankings();
FDTFluxStageKey StageKey = FDTFluxStageKey(ContestId, Contest.GetLastStageId()); FDTFluxStageKey StageKey = FDTFluxStageKey(ContestId, Contest.GetLastStageId());
FDTFluxStageRankings TempStageRankings; FDTFluxStageRankings TempStageRankings;
//Obtenir les ranking Frais. //Obtenir les ranking Frais.
CoreSubsystem->GetStageRankingsWithKey(StageKey, TempStageRankings, false); CoreSubsystem->GetStageRankingsWithKey(StageKey, TempStageRankings, true);
PendingStageRanking.Add(StageKey, false); AllRankings.Add(TempStageRankings);
LaunchPursuitSequence();
// CoreSubsystem->GetStageRankings()
} }
} }
} }
void UDTFluxPursuitManager::SetPursuitInfoIsMassStart(FDTFluxPursuitGroup NextFocusGroup) void UDTFluxPursuitManager::SetPursuitInfoIsMassStart(FDTFluxPursuitGroup& NextFocusGroup)
{ {
for (auto& Pursuit : NextFocusGroup.PursuitGroup) for (auto& Pursuit : NextFocusGroup.PursuitGroup)
{ {
@ -45,62 +48,144 @@ void UDTFluxPursuitManager::SetPursuitInfoIsMassStart(FDTFluxPursuitGroup NextFo
} }
} }
void UDTFluxPursuitManager::GetPursuit(TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext, void UDTFluxPursuitManager::GetPursuit(TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext,
TArray<FDTFluxPursuitInfo>& OutPursuitNext, bool& BIsFocusTruncate, TArray<FDTFluxPursuitInfo>& OutPursuitNext, bool& BIsFocusTruncate,
const int MaxSimultaneousPursuit) 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) if (MaxSimultaneousPursuit <= 0)
{ {
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("MaxSimultaneousPursuit must be > 0")); UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Invalid MaxSimultaneousPursuit: %d"), MaxSimultaneousPursuit);
OutPursuitFocusNext = TArray<FDTFluxPursuitInfo>(); OutPursuitFocusNext.Reset();
OutPursuitNext = TArray<FDTFluxPursuitInfo>(); OutPursuitNext.Reset();
BIsFocusTruncate = false; BIsFocusTruncate = false;
return; return;
} }
if (bIsSequenceDone && MaxSimultaneousPursuit <= 0) if (bIsSequenceDone || GroupedPursuit.IsEmpty())
{ {
OutPursuitFocusNext = TArray<FDTFluxPursuitInfo>(); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("No groups available or sequence completed"));
OutPursuitNext = TArray<FDTFluxPursuitInfo>(); OutPursuitFocusNext.Reset();
OutPursuitNext.Reset();
BIsFocusTruncate = false; BIsFocusTruncate = false;
return; 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(); OutPursuitFocusNext.Reset();
OutPursuitNext.Reset(); OutPursuitNext.Reset();
if (!GroupedPursuit.IsEmpty())
if (GroupedPursuit.IsEmpty())
{ {
FDTFluxPursuitGroup NextFocusGroup = GroupedPursuit[0]; UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("All groups were expired and removed - no groups available"));
GroupedPursuit.RemoveAt(0); OutPursuitFocusNext.Reset();
SetPursuitInfoIsMassStart(NextFocusGroup); OutPursuitNext.Reset();
OutPursuitFocusNext = NextFocusGroup.PursuitGroup; BIsFocusTruncate = false;
bFocusIsTruncate = NextFocusGroup.PursuitGroup.Num() > 1; bIsSequenceDone = true;
for (int RemainingPursuitNum = MaxSimultaneousPursuit - 1; RemainingPursuitNum != 0;) 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)
{ {
if (!GroupedPursuit.IsEmpty()) continue;
{ }
FDTFluxPursuitGroup NextGroup = GroupedPursuit[0];
SetPursuitInfoIsMassStart(NextGroup); int32 AvailableInGroup = NextGroup.PursuitGroup.Num();
if (NextGroup.PursuitGroup.Num() >= RemainingPursuitNum) int32 NeededFromGroup = FMath::Min(TargetNextCount - AddedNextCount, AvailableInGroup);
{
// extract the number we need UE_LOG(logDTFluxCoreSubsystem, Warning,
for (int i = 0; i < RemainingPursuitNum; i++) TEXT("Processing Next Group %d: StartTime=%s, Available=%d, Taking=%d"),
{ GroupIndex,
FDTFluxPursuitInfo Pursuit = NextGroup.PursuitGroup[0]; *NextGroup.StartTimeGlobal.ToString(),
OutPursuitNext.Add(Pursuit); AvailableInGroup,
} NeededFromGroup);
break;
} for (int32 ParticipantIndex = 0; ParticipantIndex < NeededFromGroup; ParticipantIndex++)
else {
{ FDTFluxPursuitInfo NextParticipant = NextGroup.PursuitGroup[ParticipantIndex]; // Copie
OutPursuitNext.Append(NextGroup.PursuitGroup);
RemainingPursuitNum -= NextGroup.PursuitGroup.Num(); NextParticipant.bIsMassStart = NextParticipant.StartTime >= MassStartTime;
}
} OutPursuitNext.Add(NextParticipant);
else AddedNextCount++;
{
break; UE_LOG(logDTFluxCoreSubsystem, VeryVerbose,
} TEXT("Added to Next: Bib %d from Group %d"),
NextParticipant.Bib, GroupIndex);
} }
} }
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("=== PURSUIT RESULTS ==="));
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Focus: %d participants"), OutPursuitFocusNext.Num());
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Next: %d participants"), OutPursuitNext.Num());
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Remaining groups for future: %d"), GroupedPursuit.Num());
if (OutPursuitFocusNext.Num() > 0)
{
DebugFocusNext(OutPursuitFocusNext);
}
// Log détaillé des Next (limité pour éviter spam)
if (OutPursuitNext.Num() > 0)
{
DebugOutPoursuitNext(OutPursuitNext);
}
// Vérifier si la séquence est terminée
if (GroupedPursuit.IsEmpty())
{
bIsSequenceDone = true;
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Pursuit sequence will be completed after this round"));
}
FTimespan Duration = FDateTime::UtcNow() - MetricsStartFunction;
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Metrics Time Out: %d fraction seconds"),
Duration.GetDuration().GetFractionMicro());
} }
bool UDTFluxPursuitManager::InitSubSystems() bool UDTFluxPursuitManager::InitSubSystems()
@ -119,7 +204,7 @@ bool UDTFluxPursuitManager::BindRankings()
{ {
if (!bIsRankingBounded) if (!bIsRankingBounded)
{ {
CoreSubsystem->OnRequestedStageRankings.AddDynamic(this, &UDTFluxPursuitManager::OnRankingsReceived); CoreSubsystem->OnStageRankings.AddDynamic(this, &UDTFluxPursuitManager::OnRankingsReceived);
bIsRankingBounded = true; bIsRankingBounded = true;
} }
return bIsRankingBounded; return bIsRankingBounded;
@ -134,7 +219,7 @@ void UDTFluxPursuitManager::UnbindRankings()
{ {
if (bIsRankingBounded) if (bIsRankingBounded)
{ {
CoreSubsystem->OnRequestedStageRankings.RemoveDynamic(this, &UDTFluxPursuitManager::OnRankingsReceived); CoreSubsystem->OnStageRankings.RemoveDynamic(this, &UDTFluxPursuitManager::OnRankingsReceived);
bIsRankingBounded = false; bIsRankingBounded = false;
return; return;
} }
@ -178,18 +263,30 @@ bool UDTFluxPursuitManager::LaunchPursuitSequence()
AllPursuits.Add(PursuitInfo); AllPursuits.Add(PursuitInfo);
} }
} }
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("AllPursuits.Num() = %i"), AllPursuits.Num());
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)
@ -199,18 +296,17 @@ bool UDTFluxPursuitManager::LaunchPursuitSequence()
TMap<FDateTime, int> StartTimeFrequency; TMap<FDateTime, int> StartTimeFrequency;
int32 MaxFrequency = 0; int32 MaxFrequency = 0;
GroupedPursuit.Reserve(TempGroups.Num()); GroupedPursuit.Reserve(TempGroups.Num());
// parcours du TMap
for (const auto& Pair : TempGroups) for (const auto& Pair : TempGroups)
{ {
if (Pair.Value.StartTimeGlobal != FDateTime::MinValue() && Pair.Value.StartTimeGlobal != FDateTime::MaxValue()) if (Pair.Value.StartTimeGlobal != FDateTime::MinValue() && Pair.Value.StartTimeGlobal != FDateTime::MaxValue())
{ {
StartTimeFrequency.FindOrAdd(Pair.Value.StartTimeGlobal)++; int& CurrentFreq = StartTimeFrequency.FindOrAdd(Pair.Value.StartTimeGlobal, 0);
const FDateTime& PropertyValue = Pair.Value.StartTimeGlobal; // Votre propriété CurrentFreq = Pair.Value.PursuitGroup.Num();
int32& Count = StartTimeFrequency.FindOrAdd(PropertyValue, 0); if (CurrentFreq > MaxFrequency)
Count++;
if (Count > MaxFrequency)
{ {
MaxFrequency = Count; MaxFrequency = CurrentFreq;
MassStartTime = PropertyValue; MassStartTime = Pair.Value.StartTimeGlobal;
} }
} }
GroupedPursuit.Add(Pair.Value); GroupedPursuit.Add(Pair.Value);
@ -226,7 +322,29 @@ bool UDTFluxPursuitManager::LaunchPursuitSequence()
bool bIsFocusTruncate = false; bool bIsFocusTruncate = false;
GetPursuit(FocusPursuits, NextPursuits, bIsFocusTruncate); GetPursuit(FocusPursuits, NextPursuits, bIsFocusTruncate);
FPursuitStaterData PursuitData = FPursuitStaterData(FocusPursuits, NextPursuits, MassStartTime, bIsFocusTruncate); FPursuitStarterData PursuitData = FPursuitStarterData(FocusPursuits, NextPursuits, MassStartTime, bIsFocusTruncate);
CoreSubsystem->OnPursuitSequenceReady.Broadcast(PursuitData); OnPursuitSequenceReady.Broadcast(PursuitData);
return true; return true;
} }
void UDTFluxPursuitManager::DebugFocusNext(const TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext)
{
FString FocusBibs;
for (const auto& Pursuit : OutPursuitFocusNext)
{
FocusBibs += FString::Printf(TEXT("%d "), Pursuit.Bib);
}
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Focus Bibs: %s"), *FocusBibs);
}
void UDTFluxPursuitManager::DebugOutPoursuitNext(const TArray<FDTFluxPursuitInfo>& OutPursuitNext)
{
FString NextBibs;
for (int32 i = 0; i < OutPursuitNext.Num(); i++)
{
NextBibs += FString::Printf(TEXT("%d "), OutPursuitNext[i].Bib);
}
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Next Bibs: %s"), *NextBibs);
}

View File

@ -1,6 +1,8 @@
#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 "Types/Struct/FDTFluxPursuitInfo.h"
#include "Subsystems/EngineSubsystem.h" #include "Subsystems/EngineSubsystem.h"
@ -17,34 +19,6 @@ class UDTFluxModelAsset;
class UDTFluxPursuitManager; class UDTFluxPursuitManager;
struct FDTFluxServerResponse; struct FDTFluxServerResponse;
USTRUCT(BlueprintType)
struct FPursuitStaterData
{
GENERATED_BODY()
public:
FPursuitStaterData() = default;
FPursuitStaterData(const TArray<FDTFluxPursuitInfo>& InPursuitFocusNext,
const TArray<FDTFluxPursuitInfo>& InPursuitNext, const FDateTime& InMassStartTime,
const bool InIsFocusTruncate)
: PursuitFocusNext(InPursuitFocusNext), PursuitNext(InPursuitNext), MassStartTime(InMassStartTime),
bIsFocusTruncate(InIsFocusTruncate)
{
};
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
TArray<FDTFluxPursuitInfo> PursuitFocusNext = TArray<FDTFluxPursuitInfo>();
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
TArray<FDTFluxPursuitInfo> PursuitNext = TArray<FDTFluxPursuitInfo>();
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
FDateTime MassStartTime = FDateTime::MinValue();
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
bool bIsFocusTruncate = false;
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPursuitSequenceReady, const FPursuitStaterData, PursuitInfoSequenceItem);
/** /**
* *
@ -55,18 +29,43 @@ 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;
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnTeamList); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnTeamList);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
@ -76,67 +75,104 @@ public:
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnTeamStatusUpdate OnTeamStatusUpdate; FOnTeamStatusUpdate OnTeamStatusUpdate;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnRequestedStageRankings, const FDTFluxStageKey, StageKey,
const FDTFluxStageRankings, StageRankings);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnRequestedStageRankings OnRequestedStageRankings;
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnPursuitSequenceReady OnPursuitSequenceReady;
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetContestRankings(const int ContestId, FDTFluxContestRankings& OutContestRankings);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetStageRankings(const int ContestId, const int StageId, FDTFluxStageRankings& OutStageRankings);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetSplitRankings(const int ContestId, const int StageId, const int SplitId,
FDTFluxSplitRankings& OutSplitRankings);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetStageRankingsWithKey(const FDTFluxStageKey StageKey, FDTFluxStageRankings& OutStageRankings,
const bool bShouldUseCached = true);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetSplitRankingsWithKey(const FDTFluxSplitKey SplitKey, FDTFluxSplitRankings& OutSplitRankings,
const bool bShouldUseCached = true);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
TArray<FGuid> TrackedRequestContestRankings(const TArray<int> ForContests);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
TArray<FGuid> TrackedRequestStageRankings(const TArray<FDTFluxStageKey> ForStages);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
TArray<FGuid> TrackedRequestSplitRankings(const TArray<FDTFluxSplitKey> ForSplits);
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintReadOnly, Category="DTFlux|Core Subsystem")
UDTFluxPursuitManager* PursuitManager = nullptr; UDTFluxPursuitManager* PursuitManager = nullptr;
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSplitSensor, FDTFluxSplitSensorInfo, SplitSensorInfo);
const FDTFluxParticipant GetParticipant(int InBib);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnSplitSensor OnSplitSensor;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnFinisher, FDTFluxSplitSensorInfo, SplitSensorInfo);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnFinisher OnFinisher;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPreFinish, FDTFluxSplitSensorInfo, SplitSensorInfo);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnPreFinish OnPreFinish;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWinner, FDTFluxSplitSensorInfo, SplitSensorInfo);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnWinner OnWinner;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnParticipantTrackingReady, FDTFluxSplitHistory, SplitHistory);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnParticipantTrackingReady OnParticipantTrackingReady;
//TODO : this must be a ProjectSetting //TODO : this must be a ProjectSetting
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintReadOnly, Category="DTFlux|Core Subsystem")
bool bShouldKeepRankings = true; bool bShouldKeepRankings = true;
UFUNCTION()
TArray<int> GetCurrentContestsId();
UFUNCTION()
TArray<FDTFluxContest> GetCurrentContests();
UFUNCTION()
TArray<int> GetContestsIdForTime(const FDateTime Time) const;
UFUNCTION()
bool GetContestForId(const int Id, FDTFluxContest& OutContest);
UFUNCTION()
TArray<FDTFluxContest> GetContestsForTime(const FDateTime Time);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void InitParticipantTracking(const int Bib, const int ContestId, const int StageId);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
FGuid InitContestRankingsDisplay(const int ContestIds);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
FGuid InitStageRankingsDisplay(const int ContestId, const int StageId);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
FGuid InitSplitRankingsDisplay(const int ContestId, const int StageId, const int SplitId);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetStageRankingForBib(const int ContestId, const int StageId, const int Bib,
FDTFluxStageRanking& OutStageRankings);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetSplitRankingForBib(const int ContestId, const int StageId, const int SplitId, const int Bib,
FDTFluxSplitRanking& OutSplitRankings);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetContestRankings(const int ContestId, FDTFluxContestRankings& OutContestRankings);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetStageRankings(const int ContestId, const int StageId, FDTFluxStageRankings& OutStageRankings);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetSplitRankings(const int ContestId, const int StageId, const int SplitId,
FDTFluxSplitRankings& OutSplitRankings);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetStageRankingsWithKey(const FDTFluxStageKey StageKey, FDTFluxStageRankings& OutStageRankings,
const bool bShouldUseCached = true);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetSplitRankingsWithKey(const FDTFluxSplitKey SplitKey, FDTFluxSplitRankings& OutSplitRankings,
const bool bShouldUseCached = true);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
TArray<FGuid> TrackedRequestContestRankings(const TArray<int> ForContests, bool bEnableCache = true);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
TArray<FGuid> TrackedRequestStageRankings(const TArray<FDTFluxStageKey> ForStages, bool bEnableCache = true);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
TArray<FGuid> TrackedRequestSplitRankings(const TArray<FDTFluxSplitKey> ForSplits, bool bEnableCache = true);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetParticipant(int InBib, FDTFluxParticipant& OutParticipant);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
TArray<int> GetCurrentContestsId();
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetCurrentContests(TArray<FDTFluxContest>& OutContests);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
TArray<int> GetContestsIdForTime(const FDateTime Time);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetContestForId(const int Id, FDTFluxContest& OutContest);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetContestsForTime(const FDateTime Time, TArray<FDTFluxContest>& OutContests);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetContests(TArray<FDTFluxContest>& OutContests);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void GetContest(const int ContestId, FDTFluxContest& OutContest);
UFUNCTION() UFUNCTION()
void RequestRankingsForStages(const TArray<FDTFluxStage> RequestedStages) const; bool GetStageDefinition(const FDTFluxStageKey StageKey, FDTFluxStage& OutStageDefinition);
UFUNCTION() UFUNCTION()
TArray<FDTFluxContest> GetContests(); 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
@ -173,4 +209,11 @@ private:
void SendRequest(const FString& Message); void SendRequest(const FString& Message);
UFUNCTION() UFUNCTION()
void RegisterDelegates(); void RegisterDelegates();
UFUNCTION()
bool IsStageRankingSealed(FDTFluxStageKey StageKey);
UFUNCTION()
bool IsContestRankingSealed(int ContestId);
EDTFluxFinisherType GetSplitSensorType(const FDTFluxSplitSensorInfo& SplitSensorInfo);
}; };

View File

@ -29,6 +29,7 @@ public:
UFUNCTION(Blueprintable, Category="DTFlux|Core Subsystem|Tools") UFUNCTION(Blueprintable, Category="DTFlux|Core Subsystem|Tools")
static float ConvertTimeStringToSeconds(const FString& TimeString); static float ConvertTimeStringToSeconds(const FString& TimeString);
UFUNCTION(Blueprintable, Category="DTFlux|Core Subsystem|Tools") UFUNCTION(Blueprintable, Category="DTFlux|Core Subsystem|Tools")
static bool CompareTimeString(const FString& A_TimeStr, const FString& B_TimeStr, bool bAscendant = true); static bool CompareTimeString(const FString& A_TimeStr, const FString& B_TimeStr, bool bAscendant = true);

View File

@ -10,44 +10,31 @@
class UDTFluxCoreSubsystem; class UDTFluxCoreSubsystem;
USTRUCT()
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), ContestId(-1)
{ {
}; };
/** 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()
@ -55,6 +42,7 @@ struct FDTFluxPursuitGroup
{ {
GENERATED_BODY() GENERATED_BODY()
UPROPERTY() UPROPERTY()
TArray<FDTFluxPursuitInfo> PursuitGroup = TArray<FDTFluxPursuitInfo>(); TArray<FDTFluxPursuitInfo> PursuitGroup = TArray<FDTFluxPursuitInfo>();
UPROPERTY() UPROPERTY()
@ -65,7 +53,6 @@ struct FDTFluxPursuitGroup
bool bIsFocus = false; bool bIsFocus = false;
}; };
/** /**
* *
*/ */
@ -78,14 +65,18 @@ public:
UDTFluxPursuitManager(const FObjectInitializer& ObjectInitializer); UDTFluxPursuitManager(const FObjectInitializer& ObjectInitializer);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPursuitSequenceReady, const FPursuitStarterData, PursuitData);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnPursuitSequenceReady OnPursuitSequenceReady;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
bool bFocusIsTruncate = false; bool bFocusIsTruncate = false;
UPROPERTY() UPROPERTY()
int PursuitMaxSimultaneousPursuit = 7; int PursuitMaxSimultaneousPursuit = 7;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="DTFlux|Pursuit", UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="DTFlux|Pursuit")
meta=(ClampMin="1", ClampMax="60", UIMin="0", UIMax="60"))
int MassStartDelay = 10; int MassStartDelay = 10;
UPROPERTY() UPROPERTY()
@ -117,6 +108,10 @@ public:
UFUNCTION() UFUNCTION()
void OnRankingsReceived(const FDTFluxStageKey NewStageKey, const FDTFluxStageRankings NewStageRankings); 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; TMap<FDTFluxStageKey, bool> PendingStageRanking;
TArray<FDTFluxStageRankings> AllRankings; TArray<FDTFluxStageRankings> AllRankings;
@ -128,7 +123,7 @@ private:
UPROPERTY() UPROPERTY()
bool bIsRankingBounded = false; bool bIsRankingBounded = false;
UFUNCTION() UFUNCTION()
void SetPursuitInfoIsMassStart(FDTFluxPursuitGroup NextFocusGroup); void SetPursuitInfoIsMassStart(FDTFluxPursuitGroup& NextFocusGroup);
UFUNCTION() UFUNCTION()
bool LaunchPursuitSequence(); bool LaunchPursuitSequence();
}; };

View File

@ -23,11 +23,6 @@ bool FDTFluxTrackedRequest::CanRetry() const
(State == EDTFluxRequestState::Failed || State == EDTFluxRequestState::TimedOut); (State == EDTFluxRequestState::Failed || State == EDTFluxRequestState::TimedOut);
} }
bool FDTFluxTrackedRequest::IsCacheValid() const
{
if (State != EDTFluxRequestState::Cached) return false;
return (FDateTime::Now() - CompletedAt).GetTotalSeconds() < Config.CacheValiditySeconds;
}
float FDTFluxTrackedRequest::GetRetryDelay() const float FDTFluxTrackedRequest::GetRetryDelay() const
{ {
@ -40,11 +35,6 @@ bool FDTFluxTrackedRequest::Matches(EDTFluxApiDataType InType, int32 InContestId
return RequestType == InType && ContestId == InContestId && StageId == InStageId && SplitId == InSplitId; return RequestType == InType && ContestId == InContestId && StageId == InStageId && SplitId == InSplitId;
} }
FString FDTFluxTrackedRequest::GetCacheKey() const
{
return FString::Printf(TEXT("%s_%d_%d_%d"),
*UEnum::GetValueAsString(RequestType), ContestId, StageId, SplitId);
}
void FDTFluxTrackedRequest::SetRawResponse(const FString& RawData) void FDTFluxTrackedRequest::SetRawResponse(const FString& RawData)
{ {
@ -118,8 +108,8 @@ void FDTFluxQueuedRequestManager::Initialize(const FDTFluxRequestConfig& InDefau
DefaultConfig = InDefaultConfig; DefaultConfig = InDefaultConfig;
bIsInitialized.store(true); bIsInitialized.store(true);
UE_LOG(logDTFluxNetwork, Log, TEXT("RequestManager initialized with timeout=%.1fs, cache=%.1fs"), UE_LOG(logDTFluxNetwork, Log, TEXT("RequestManager initialized with timeout=%.1fs"),
DefaultConfig.TimeoutSeconds, DefaultConfig.CacheValiditySeconds); DefaultConfig.TimeoutSeconds);
} }
void FDTFluxQueuedRequestManager::Shutdown() void FDTFluxQueuedRequestManager::Shutdown()
@ -134,7 +124,6 @@ void FDTFluxQueuedRequestManager::Shutdown()
FScopeLock CallbacksLock_Local(&CallbacksLock); FScopeLock CallbacksLock_Local(&CallbacksLock);
AllRequests.Empty(); AllRequests.Empty();
CacheKeyToRequestId.Empty();
SuccessCallbacks.Empty(); SuccessCallbacks.Empty();
ErrorCallbacks.Empty(); ErrorCallbacks.Empty();
} }
@ -154,37 +143,7 @@ FGuid FDTFluxQueuedRequestManager::CreateTrackedRequest(
UE_LOG(logDTFluxNetwork, Error, TEXT("RequestManager not initialized")); UE_LOG(logDTFluxNetwork, Error, TEXT("RequestManager not initialized"));
return FGuid(); return FGuid();
} }
// Create new request
// Vérifier le cache d'abord
FString CachedResponse;
if (CustomConfig.bEnableCache && GetFromCache(RequestType, CachedResponse, ContestId, StageId, SplitId))
{
UE_LOG(logDTFluxNetwork, Log, TEXT("Request served from cache: Type=%s"),
*UEnum::GetValueAsString(RequestType));
// Créer une "fausse" requête pour représenter le hit cache
auto CachedRequest = MakeShared<FDTFluxTrackedRequest>();
CachedRequest->RequestType = RequestType;
CachedRequest->ContestId = ContestId;
CachedRequest->StageId = StageId;
CachedRequest->SplitId = SplitId;
CachedRequest->Config = CustomConfig.bEnableCache ? CustomConfig : DefaultConfig;
CachedRequest->State = EDTFluxRequestState::Cached;
CachedRequest->RawResponseData = CachedResponse;
CachedRequest->CompletedAt = FDateTime::Now();
FGuid CacheRequestId = CachedRequest->RequestId;
{
FScopeLock Lock(&RequestsLock);
AllRequests.Add(CacheRequestId, CachedRequest);
}
RecordCacheHit();
return CacheRequestId;
}
// Créer une nouvelle requête
auto NewRequest = MakeShared<FDTFluxTrackedRequest>(); auto NewRequest = MakeShared<FDTFluxTrackedRequest>();
NewRequest->RequestType = RequestType; NewRequest->RequestType = RequestType;
NewRequest->ContestId = ContestId; NewRequest->ContestId = ContestId;
@ -199,12 +158,8 @@ FGuid FDTFluxQueuedRequestManager::CreateTrackedRequest(
AllRequests.Add(RequestId, NewRequest); AllRequests.Add(RequestId, NewRequest);
TotalRequests++; TotalRequests++;
} }
RecordCacheMiss();
UE_LOG(logDTFluxNetwork, Log, TEXT("Created tracked request %s: Type=%s, Contest=%d, Stage=%d, Split=%d"), 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); *RequestId.ToString(), *UEnum::GetValueAsString(RequestType), ContestId, StageId, SplitId);
return RequestId; return RequestId;
} }
@ -257,7 +212,6 @@ bool FDTFluxQueuedRequestManager::CompleteRequest(const FGuid& RequestId, const
{ {
UE_LOG(logDTFluxNetwork, Log, TEXT("FDTFluxQueuedRequestManager::CompleteRequest() %s"), *RequestId.ToString()); UE_LOG(logDTFluxNetwork, Log, TEXT("FDTFluxQueuedRequestManager::CompleteRequest() %s"), *RequestId.ToString());
TSharedPtr<FDTFluxTrackedRequest> Request; TSharedPtr<FDTFluxTrackedRequest> Request;
{ {
FScopeLock Lock(&RequestsLock); FScopeLock Lock(&RequestsLock);
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId)) if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
@ -265,19 +219,18 @@ bool FDTFluxQueuedRequestManager::CompleteRequest(const FGuid& RequestId, const
Request = *RequestPtr; Request = *RequestPtr;
} }
} }
if (!Request.IsValid()) if (!Request.IsValid())
{ {
UE_LOG(logDTFluxNetwork, Warning, TEXT("Request %s not found"), *RequestId.ToString()); UE_LOG(logDTFluxNetwork, Warning, TEXT("Request %s not found"), *RequestId.ToString());
return false; return false;
} }
// Stocker la réponse brute // Store RawResponse
Request->SetRawResponse(RawResponseData); Request->SetRawResponse(RawResponseData);
Request->CompletedAt = FDateTime::Now(); Request->CompletedAt = FDateTime::Now();
UE_LOG(logDTFluxNetwork, Log, TEXT("Request %s completed at %s"), *RequestId.ToString(), UE_LOG(logDTFluxNetwork, Log, TEXT("Request %s completed at %s"), *RequestId.ToString(),
*Request->CompletedAt.ToString()); *Request->CompletedAt.ToString());
// Décider du parsing selon les callbacks et la configuration // Decide to parse based upon config
bool bHasCallbacks = false; bool bHasCallbacks = false;
{ {
FScopeLock Lock(&CallbacksLock); FScopeLock Lock(&CallbacksLock);
@ -292,7 +245,7 @@ bool FDTFluxQueuedRequestManager::CompleteRequest(const FGuid& RequestId, const
bHasCallbacks ? TEXT("true") : TEXT("false"), bUseAsyncParsing ? TEXT("true") : TEXT("false"), bHasCallbacks ? TEXT("true") : TEXT("false"), bUseAsyncParsing ? TEXT("true") : TEXT("false"),
RawResponseData.IsEmpty() ? TEXT("true") : TEXT("false")); RawResponseData.IsEmpty() ? TEXT("true") : TEXT("false"));
// Parsing asynchrone pour les callbacks // Async parsing for Cb
FOnParsingCompleted OnCompleted = FOnParsingCompleted::CreateRaw( FOnParsingCompleted OnCompleted = FOnParsingCompleted::CreateRaw(
this, &FDTFluxQueuedRequestManager::OnParsingCompleted this, &FDTFluxQueuedRequestManager::OnParsingCompleted
); );
@ -300,9 +253,8 @@ bool FDTFluxQueuedRequestManager::CompleteRequest(const FGuid& RequestId, const
FOnParsingFailed OnFailed = FOnParsingFailed::CreateRaw( FOnParsingFailed OnFailed = FOnParsingFailed::CreateRaw(
this, &FDTFluxQueuedRequestManager::OnParsingFailed this, &FDTFluxQueuedRequestManager::OnParsingFailed
); );
// Maybe send to parser in another place
AsyncParser->ParseResponseAsync(RequestId, RawResponseData, OnCompleted, OnFailed); AsyncParser->ParseResponseAsync(RequestId, RawResponseData, OnCompleted, OnFailed);
// CleanupCallbacks(RequestId);
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Started async parsing for request %s"), *RequestId.ToString()); UE_LOG(logDTFluxNetwork, Verbose, TEXT("Started async parsing for request %s"), *RequestId.ToString());
return true; return true;
} }
@ -310,26 +262,21 @@ bool FDTFluxQueuedRequestManager::CompleteRequest(const FGuid& RequestId, const
{ {
UE_LOG(logDTFluxNetwork, Warning, TEXT("request %s completed without sync"), *RequestId.ToString()); UE_LOG(logDTFluxNetwork, Warning, TEXT("request %s completed without sync"), *RequestId.ToString());
// Compléter immédiatement sans parsing ou avec parsing sync // Compléter immédiatement sans parsing ou avec parsing sync
EDTFluxRequestState NewState = Request->Config.bEnableCache EDTFluxRequestState NewState = EDTFluxRequestState::Completed;
? EDTFluxRequestState::Cached
: EDTFluxRequestState::Completed;
ChangeRequestState(Request, NewState); ChangeRequestState(Request, NewState);
if (Request->Config.bEnableCache)
{
FScopeLock Lock(&RequestsLock);
CacheKeyToRequestId.Add(Request->GetCacheKey(), RequestId);
}
// Déclencher les callbacks avec les données brutes // Déclencher les callbacks avec les données brutes
TriggerCallbacks(*Request); TriggerCallbacks(*Request);
CleanupCallbacks(RequestId); CleanupCallbacks(RequestId);
return true; return true;
} }
} }
/**
* @todo Check protocol errors ???
* @param RequestId
* @param ErrorMessage
* @return
*/
bool FDTFluxQueuedRequestManager::FailRequest(const FGuid& RequestId, const FString& ErrorMessage) bool FDTFluxQueuedRequestManager::FailRequest(const FGuid& RequestId, const FString& ErrorMessage)
{ {
TSharedPtr<FDTFluxTrackedRequest> Request; TSharedPtr<FDTFluxTrackedRequest> Request;
@ -350,7 +297,6 @@ bool FDTFluxQueuedRequestManager::FailRequest(const FGuid& RequestId, const FStr
Request->LastErrorMessage = ErrorMessage; Request->LastErrorMessage = ErrorMessage;
ChangeRequestState(Request, EDTFluxRequestState::Failed); ChangeRequestState(Request, EDTFluxRequestState::Failed);
// Déclencher les callbacks d'erreur
TriggerCallbacks(*Request); TriggerCallbacks(*Request);
CleanupCallbacks(RequestId); CleanupCallbacks(RequestId);
@ -407,76 +353,6 @@ bool FDTFluxQueuedRequestManager::FindPendingRequest(
return false; return false;
} }
bool FDTFluxQueuedRequestManager::GetFromCache(
EDTFluxApiDataType RequestType,
FString& OutRawResponse,
int32 ContestId,
int32 StageId,
int32 SplitId) const
{
FString CacheKey = GenerateCacheKey(RequestType, ContestId, StageId, SplitId);
FScopeLock Lock(&RequestsLock);
if (const FGuid* RequestId = CacheKeyToRequestId.Find(CacheKey))
{
if (const TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(*RequestId))
{
const TSharedPtr<FDTFluxTrackedRequest>& CachedRequest = *RequestPtr;
if (CachedRequest->IsCacheValid() && !CachedRequest->RawResponseData.IsEmpty())
{
OutRawResponse = CachedRequest->RawResponseData;
return true;
}
}
}
return false;
}
bool FDTFluxQueuedRequestManager::GetParsedFromCache(
EDTFluxApiDataType RequestType,
TSharedPtr<FDTFluxServerResponse>& OutResponse,
int32 ContestId,
int32 StageId,
int32 SplitId) const
{
FString CacheKey = GenerateCacheKey(RequestType, ContestId, StageId, SplitId);
FScopeLock Lock(&RequestsLock);
if (const FGuid* RequestId = CacheKeyToRequestId.Find(CacheKey))
{
if (const TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(*RequestId))
{
const TSharedPtr<FDTFluxTrackedRequest>& CachedRequest = *RequestPtr;
if (CachedRequest->IsCacheValid())
{
// Parsing lazy si nécessaire
if (!CachedRequest->ParsedResponse.IsSet() && !CachedRequest->RawResponseData.IsEmpty())
{
OutResponse = AsyncParser->ParseResponseSync(CachedRequest->RawResponseData, 1.0f);
if (OutResponse.IsValid())
{
CachedRequest->ParsedResponse = OutResponse;
const_cast<FDTFluxTrackedRequest*>(CachedRequest.Get())->bIsResponseParsed = true;
}
}
else if (CachedRequest->ParsedResponse.IsSet())
{
OutResponse = CachedRequest->ParsedResponse.GetValue();
}
return OutResponse.IsValid();
}
}
}
return false;
}
// === ACCESSEURS === // === ACCESSEURS ===
bool FDTFluxQueuedRequestManager::GetRequest(const FGuid& RequestId, FDTFluxTrackedRequest& OutRequest) const bool FDTFluxQueuedRequestManager::GetRequest(const FGuid& RequestId, FDTFluxTrackedRequest& OutRequest) const
@ -554,9 +430,6 @@ FDTFluxQueuedRequestManager::FRequestStatistics FDTFluxQueuedRequestManager::Get
case EDTFluxRequestState::Retrying: case EDTFluxRequestState::Retrying:
Stats.Pending++; Stats.Pending++;
break; break;
case EDTFluxRequestState::Cached:
Stats.Cached++;
break;
case EDTFluxRequestState::Completed: case EDTFluxRequestState::Completed:
Stats.Completed++; Stats.Completed++;
break; break;
@ -568,46 +441,10 @@ FDTFluxQueuedRequestManager::FRequestStatistics FDTFluxQueuedRequestManager::Get
} }
Stats.TotalRequests = TotalRequests; Stats.TotalRequests = TotalRequests;
Stats.CacheHits = CacheHits;
Stats.CacheMisses = CacheMisses;
if (Stats.TotalRequests > 0)
{
Stats.HitRate = ((float)Stats.CacheHits / (float)Stats.TotalRequests) * 100.0f;
}
return Stats; return Stats;
} }
// === NETTOYAGE === // === NETTOYAGE ===
int32 FDTFluxQueuedRequestManager::CleanupExpiredCache()
{
FScopeLock Lock(&RequestsLock);
TArray<FGuid> ExpiredRequests;
for (const auto& [RequestId, Request] : AllRequests)
{
if (Request->State == EDTFluxRequestState::Cached && !Request->IsCacheValid())
{
ExpiredRequests.Add(RequestId);
}
}
for (const FGuid& RequestId : ExpiredRequests)
{
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
{
const TSharedPtr<FDTFluxTrackedRequest>& Request = *RequestPtr;
CacheKeyToRequestId.Remove(Request->GetCacheKey());
AllRequests.Remove(RequestId);
}
}
return ExpiredRequests.Num();
}
int32 FDTFluxQueuedRequestManager::CleanupCompletedRequests(float OlderThanSeconds) int32 FDTFluxQueuedRequestManager::CleanupCompletedRequests(float OlderThanSeconds)
{ {
FScopeLock Lock(&RequestsLock); FScopeLock Lock(&RequestsLock);
@ -638,7 +475,6 @@ void FDTFluxQueuedRequestManager::ClearAllRequests()
FScopeLock CallbacksLock_Local(&CallbacksLock); FScopeLock CallbacksLock_Local(&CallbacksLock);
AllRequests.Empty(); AllRequests.Empty();
CacheKeyToRequestId.Empty();
SuccessCallbacks.Empty(); SuccessCallbacks.Empty();
ErrorCallbacks.Empty(); ErrorCallbacks.Empty();
@ -652,7 +488,6 @@ void FDTFluxQueuedRequestManager::Tick(float DeltaTime)
// Mise à jour des timers // Mise à jour des timers
TimeSinceLastTimeoutCheck += DeltaTime; TimeSinceLastTimeoutCheck += DeltaTime;
TimeSinceLastCacheCleanup += DeltaTime;
TimeSinceLastRetryCheck += DeltaTime; TimeSinceLastRetryCheck += DeltaTime;
// Vérifier les timeouts // Vérifier les timeouts
@ -668,13 +503,6 @@ void FDTFluxQueuedRequestManager::Tick(float DeltaTime)
ProcessRetries(); ProcessRetries();
TimeSinceLastRetryCheck = 0.0f; TimeSinceLastRetryCheck = 0.0f;
} }
// Nettoyage du cache
if (TimeSinceLastCacheCleanup >= CacheCleanupInterval)
{
ProcessCacheCleanup();
TimeSinceLastCacheCleanup = 0.0f;
}
} }
void FDTFluxQueuedRequestManager::ChangeRequestState(TSharedPtr<FDTFluxTrackedRequest> Request, void FDTFluxQueuedRequestManager::ChangeRequestState(TSharedPtr<FDTFluxTrackedRequest> Request,
@ -756,17 +584,11 @@ void FDTFluxQueuedRequestManager::ProcessRetries()
} }
} }
void FDTFluxQueuedRequestManager::ProcessCacheCleanup()
{
CleanupExpiredCache();
CleanupCompletedRequests(600.0f);
}
void FDTFluxQueuedRequestManager::TriggerCallbacks(const FDTFluxTrackedRequest& Request) void FDTFluxQueuedRequestManager::TriggerCallbacks(const FDTFluxTrackedRequest& Request)
{ {
FScopeLock Lock(&CallbacksLock); FScopeLock Lock(&CallbacksLock);
if (Request.State == EDTFluxRequestState::Completed || Request.State == EDTFluxRequestState::Cached) if (Request.State == EDTFluxRequestState::Completed)
{ {
// Success Cb // Success Cb
const FOnDTFluxRequestSuccess* SuccessCallback = SuccessCallbacks.Find(Request.RequestId); const FOnDTFluxRequestSuccess* SuccessCallback = SuccessCallbacks.Find(Request.RequestId);
@ -793,18 +615,6 @@ void FDTFluxQueuedRequestManager::CleanupCallbacks(const FGuid& RequestId)
ErrorCallbacks.Remove(RequestId); ErrorCallbacks.Remove(RequestId);
} }
void FDTFluxQueuedRequestManager::RecordCacheHit() const
{
FScopeLock Lock(&MetricsLock);
CacheHits++;
}
void FDTFluxQueuedRequestManager::RecordCacheMiss() const
{
FScopeLock Lock(&MetricsLock);
CacheMisses++;
}
void FDTFluxQueuedRequestManager::OnParsingCompleted(const FGuid& RequestId, void FDTFluxQueuedRequestManager::OnParsingCompleted(const FGuid& RequestId,
TSharedPtr<FDTFluxServerResponse> ParsedResponse, bool bSuccess) TSharedPtr<FDTFluxServerResponse> ParsedResponse, bool bSuccess)
{ {
@ -833,16 +643,9 @@ void FDTFluxQueuedRequestManager::OnParsingCompleted(const FGuid& RequestId,
{ {
Request->ParsedResponse = ParsedResponse; Request->ParsedResponse = ParsedResponse;
Request->bIsResponseParsed = true; Request->bIsResponseParsed = true;
EDTFluxRequestState NewState = Request->Config.bEnableCache EDTFluxRequestState NewState = EDTFluxRequestState::Completed;
? EDTFluxRequestState::Cached
: EDTFluxRequestState::Completed;
ChangeRequestState(Request, NewState); ChangeRequestState(Request, NewState);
if (Request->Config.bEnableCache)
{
FScopeLock Lock(&RequestsLock);
CacheKeyToRequestId.Add(Request->GetCacheKey(), RequestId);
}
UE_LOG(logDTFluxNetwork, Log, UE_LOG(logDTFluxNetwork, Log,
TEXT("DTFluxQueuedRequestManager: Async parsing completed for request %s"), TEXT("DTFluxQueuedRequestManager: Async parsing completed for request %s"),
*RequestId.ToString()); *RequestId.ToString());
@ -854,7 +657,6 @@ void FDTFluxQueuedRequestManager::OnParsingCompleted(const FGuid& RequestId,
UE_LOG(logDTFluxNetwork, Error, TEXT("Async parsing failed for request %s"), *RequestId.ToString()); UE_LOG(logDTFluxNetwork, Error, TEXT("Async parsing failed for request %s"), *RequestId.ToString());
} }
// ✅ FIX: Déclencher les callbacks maintenant !
TriggerCallbacks(*Request); TriggerCallbacks(*Request);
CleanupCallbacks(RequestId); CleanupCallbacks(RequestId);
} }
@ -866,13 +668,3 @@ void FDTFluxQueuedRequestManager::OnParsingFailed(const FGuid& RequestId, const
*ErrorMessage); *ErrorMessage);
FailRequest(RequestId, FString::Printf(TEXT("Parsing failed: %s"), *ErrorMessage)); FailRequest(RequestId, FString::Printf(TEXT("Parsing failed: %s"), *ErrorMessage));
} }
FString FDTFluxQueuedRequestManager::GenerateCacheKey(EDTFluxApiDataType RequestType, int32 ContestId, int32 StageId,
int32 SplitId)
{
return FString::Printf(TEXT("%s_%d_%d_%d"),
*UEnum::GetValueAsString(RequestType),
ContestId,
StageId,
SplitId);
}

View File

@ -363,8 +363,8 @@ bool FDTFluxServerResponse::ParseContestRanking(FDTFluxContestRankings& OutConte
OutContestRankings.Rankings.Add(Ranking); OutContestRankings.Rankings.Add(Ranking);
} }
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed contest ranking for Contest %d with %d entries"), // UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed contest ranking for Contest %d with %d entries"),
OutContestRankings.ContestId, OutContestRankings.Rankings.Num()); // OutContestRankings.ContestId, OutContestRankings.Rankings.Num());
ParsingStatus = EDTFluxResponseStatus::Success; ParsingStatus = EDTFluxResponseStatus::Success;
return true; return true;
} }
@ -475,12 +475,14 @@ bool FDTFluxServerResponse::ParseSplitSensor(TArray<FDTFluxSplitSensorInfo>& Out
NewSplitSensorInfo.StageId = SplitSensorInfoResponse.StageID; NewSplitSensorInfo.StageId = SplitSensorInfoResponse.StageID;
NewSplitSensorInfo.SplitId = SplitSensorInfoResponse.SplitID; NewSplitSensorInfo.SplitId = SplitSensorInfoResponse.SplitID;
NewSplitSensorInfo.Time = SplitSensorInfoResponse.Time; NewSplitSensorInfo.Time = SplitSensorInfoResponse.Time;
NewSplitSensorInfo.Gap = SplitSensorInfoResponse.Gap;
NewSplitSensorInfo.Rank = SplitSensorInfoResponse.Rank;
OutSplitSensorInfos.Add(NewSplitSensorInfo); OutSplitSensorInfos.Add(NewSplitSensorInfo);
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Split sensor info for bib %d in Contest %d, Stage %d, Split %d"), UE_LOG(logDTFluxNetwork, Verbose, TEXT("Split sensor info for bib %d in Contest %d, Stage %d, Split %d Rank [%i] Gap [%s] Time [%s]"),
NewSplitSensorInfo.Bib, NewSplitSensorInfo.ContestId, NewSplitSensorInfo.StageId, NewSplitSensorInfo.Bib, NewSplitSensorInfo.ContestId, NewSplitSensorInfo.StageId,
NewSplitSensorInfo.SplitId); NewSplitSensorInfo.SplitId, NewSplitSensorInfo.Rank, *NewSplitSensorInfo.Gap,*NewSplitSensorInfo.Time);
} }
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed %d split sensor entries"), OutSplitSensorInfos.Num()); UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed %d split sensor entries"), OutSplitSensorInfos.Num());

View File

@ -36,8 +36,6 @@ void UDTFluxNetworkSubsystem::Initialize(FSubsystemCollectionBase& Collection)
DefaultConfig.TimeoutSeconds = 5.0f; DefaultConfig.TimeoutSeconds = 5.0f;
DefaultConfig.MaxRetries = 3; DefaultConfig.MaxRetries = 3;
DefaultConfig.RetryBackoffMultiplier = 1.5f; DefaultConfig.RetryBackoffMultiplier = 1.5f;
DefaultConfig.bEnableCache = true;
DefaultConfig.CacheValiditySeconds = 60.0f;
RequestManager->Initialize(DefaultConfig); RequestManager->Initialize(DefaultConfig);
@ -143,8 +141,6 @@ FGuid UDTFluxNetworkSubsystem::SendTrackedRequest(
FDTFluxRequestConfig CustomConfig; FDTFluxRequestConfig CustomConfig;
CustomConfig.TimeoutSeconds = TimeoutSeconds; CustomConfig.TimeoutSeconds = TimeoutSeconds;
CustomConfig.MaxRetries = MaxRetries; CustomConfig.MaxRetries = MaxRetries;
CustomConfig.bEnableCache = bEnableCache;
CustomConfig.CacheValiditySeconds = 60.0f;
CustomConfig.RetryBackoffMultiplier = 1.5f; CustomConfig.RetryBackoffMultiplier = 1.5f;
FGuid RequestId = RequestManager->CreateTrackedRequest(RequestType, ContestId, StageId, SplitId, CustomConfig); FGuid RequestId = RequestManager->CreateTrackedRequest(RequestType, ContestId, StageId, SplitId, CustomConfig);
@ -154,12 +150,8 @@ FGuid UDTFluxNetworkSubsystem::SendTrackedRequest(
// Récupérer la requête pour l'envoyer // Récupérer la requête pour l'envoyer
if (const FDTFluxTrackedRequest* Request = RequestManager->GetRequestPtr(RequestId)) if (const FDTFluxTrackedRequest* Request = RequestManager->GetRequestPtr(RequestId))
{ {
// Si la requête est déjà en cache, pas besoin d'envoyer RequestManager->MarkRequestAsSent(RequestId);
if (Request->State != EDTFluxRequestState::Cached) SendQueuedRequest(*Request);
{
RequestManager->MarkRequestAsSent(RequestId);
SendQueuedRequest(*Request);
}
} }
} }
@ -174,8 +166,8 @@ FGuid UDTFluxNetworkSubsystem::SendTrackedRequestWithCallbacks(
FOnDTFluxRequestSuccess& OnSuccess, FOnDTFluxRequestSuccess& OnSuccess,
FOnDTFluxRequestError& OnError, FOnDTFluxRequestError& OnError,
float TimeoutSeconds, float TimeoutSeconds,
int32 MaxRetries, int32 MaxRetries
bool bEnableCache) )
{ {
if (!RequestManager.IsValid()) if (!RequestManager.IsValid())
{ {
@ -186,8 +178,6 @@ FGuid UDTFluxNetworkSubsystem::SendTrackedRequestWithCallbacks(
FDTFluxRequestConfig CustomConfig; FDTFluxRequestConfig CustomConfig;
CustomConfig.TimeoutSeconds = TimeoutSeconds; CustomConfig.TimeoutSeconds = TimeoutSeconds;
CustomConfig.MaxRetries = MaxRetries; CustomConfig.MaxRetries = MaxRetries;
CustomConfig.bEnableCache = bEnableCache;
CustomConfig.CacheValiditySeconds = 60.0f;
CustomConfig.RetryBackoffMultiplier = 1.5f; CustomConfig.RetryBackoffMultiplier = 1.5f;
FGuid RequestId = RequestManager->CreateTrackedRequestWithCallbacks( FGuid RequestId = RequestManager->CreateTrackedRequestWithCallbacks(
@ -197,11 +187,8 @@ FGuid UDTFluxNetworkSubsystem::SendTrackedRequestWithCallbacks(
{ {
if (const FDTFluxTrackedRequest* Request = RequestManager->GetRequestPtr(RequestId)) if (const FDTFluxTrackedRequest* Request = RequestManager->GetRequestPtr(RequestId))
{ {
if (Request->State != EDTFluxRequestState::Cached) RequestManager->MarkRequestAsSent(RequestId);
{ SendQueuedRequest(*Request);
RequestManager->MarkRequestAsSent(RequestId);
SendQueuedRequest(*Request);
}
} }
} }
@ -227,7 +214,6 @@ bool UDTFluxNetworkSubsystem::HasRequestReceivedResponse(const FGuid& RequestId)
if (GetTrackedRequest(RequestId, Request)) if (GetTrackedRequest(RequestId, Request))
{ {
return Request.State == EDTFluxRequestState::Completed || return Request.State == EDTFluxRequestState::Completed ||
Request.State == EDTFluxRequestState::Cached ||
!Request.RawResponseData.IsEmpty(); !Request.RawResponseData.IsEmpty();
} }
return false; return false;
@ -264,22 +250,19 @@ int32 UDTFluxNetworkSubsystem::GetPendingRequestCount() const
return 0; return 0;
} }
void UDTFluxNetworkSubsystem::GetRequestStatistics(int32& OutPending, int32& OutCached, int32& OutCompleted, void UDTFluxNetworkSubsystem::GetRequestStatistics(int32& OutPending, int32& OutCompleted,
int32& OutFailed, float& OutHitRate) const int32& OutFailed) const
{ {
if (RequestManager.IsValid()) if (RequestManager.IsValid())
{ {
FDTFluxQueuedRequestManager::FRequestStatistics Stats = RequestManager->GetStatistics(); FDTFluxQueuedRequestManager::FRequestStatistics Stats = RequestManager->GetStatistics();
OutPending = Stats.Pending; OutPending = Stats.Pending;
OutCached = Stats.Cached;
OutCompleted = Stats.Completed; OutCompleted = Stats.Completed;
OutFailed = Stats.Failed; OutFailed = Stats.Failed;
OutHitRate = Stats.HitRate;
} }
else else
{ {
OutPending = OutCached = OutCompleted = OutFailed = 0; OutPending = OutCompleted = OutFailed = 0;
OutHitRate = 0.0f;
} }
} }
@ -362,7 +345,7 @@ void UDTFluxNetworkSubsystem::RegisterWebSocketEvents()
&UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem); &UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem);
} }
void UDTFluxNetworkSubsystem::UnregisterWebSocketEvents() void UDTFluxNetworkSubsystem::UnregisterWebSocketEvents() const
{ {
if (!WsClient.IsValid()) return; if (!WsClient.IsValid()) return;
@ -571,9 +554,6 @@ void UDTFluxNetworkSubsystem::ReconnectWs(const FName WsClientId)
} }
} }
// ================================================================================================
// MÉTHODES DE PARSING LEGACY (COMPATIBILITÉ TOTALE)
// ================================================================================================
void UDTFluxNetworkSubsystem::ParseTeamListResponse(FDTFluxServerResponse& Response) void UDTFluxNetworkSubsystem::ParseTeamListResponse(FDTFluxServerResponse& Response)
{ {
@ -794,11 +774,10 @@ void UDTFluxNetworkSubsystem::SendQueuedRequest(const FDTFluxTrackedRequest& Que
bool UDTFluxNetworkSubsystem::ShouldUseAsyncParsing(const FString& JsonData) const bool UDTFluxNetworkSubsystem::ShouldUseAsyncParsing(const FString& JsonData) const
{ {
// Critères pour décider du parsing asynchrone : // Critère pour décider du parsing asynchrone :
// - Taille des données (> 1KB par défaut) // - Taille des données (> 1KB par défaut)
// - Charge actuelle du système // Pour le moment uniquement taille
// - Type de données (certains types sont plus complexes à parser)
const int32 AsyncThreshold = 1024; // 1KB constexpr int32 AsyncThreshold = 1024; // 1KB
return JsonData.Len() > AsyncThreshold; return JsonData.Len() > AsyncThreshold;
} }

View File

@ -26,7 +26,6 @@ enum class EDTFluxRequestState : uint8
Completed UMETA(DisplayName = "Completed"), Completed UMETA(DisplayName = "Completed"),
Failed UMETA(DisplayName = "Failed"), Failed UMETA(DisplayName = "Failed"),
TimedOut UMETA(DisplayName = "TimedOut"), TimedOut UMETA(DisplayName = "TimedOut"),
Cached UMETA(DisplayName = "Cached"),
Retrying UMETA(DisplayName = "Retrying") Retrying UMETA(DisplayName = "Retrying")
}; };
@ -43,12 +42,6 @@ struct DTFLUXNETWORK_API FDTFluxRequestConfig
UPROPERTY(EditAnywhere, BlueprintReadWrite) UPROPERTY(EditAnywhere, BlueprintReadWrite)
float RetryBackoffMultiplier = 1.5f; float RetryBackoffMultiplier = 1.5f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bEnableCache = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float CacheValiditySeconds = 60.0f;
}; };
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
@ -112,10 +105,8 @@ struct DTFLUXNETWORK_API FDTFluxTrackedRequest
bool HasTimedOut() const; bool HasTimedOut() const;
bool CanRetry() const; bool CanRetry() const;
bool IsCacheValid() const;
float GetRetryDelay() const; float GetRetryDelay() const;
bool Matches(EDTFluxApiDataType InType, int32 InContestId = -1, int32 InStageId = -1, int32 InSplitId = -1) const; bool Matches(EDTFluxApiDataType InType, int32 InContestId = -1, int32 InStageId = -1, int32 InSplitId = -1) const;
FString GetCacheKey() const;
void SetRawResponse(const FString& RawData); void SetRawResponse(const FString& RawData);
FString Serialize() const; FString Serialize() const;
}; };
@ -137,7 +128,7 @@ DECLARE_MULTICAST_DELEGATE_OneParam(FOnRequestFailedNative, const FDTFluxTracked
// ================================================================================================ // ================================================================================================
/** /**
* Gestionnaire de requêtes trackées avec cache, timeout, retry et parsing asynchrone * Gestionnaire de requêtes trackées timeout, retry et parsing asynchrone
* Implémentation C++ pure avec SmartPointers pour des performances optimales * Implémentation C++ pure avec SmartPointers pour des performances optimales
*/ */
class DTFLUXNETWORK_API FDTFluxQueuedRequestManager : public FTickableGameObject class DTFLUXNETWORK_API FDTFluxQueuedRequestManager : public FTickableGameObject
@ -229,7 +220,7 @@ public:
*/ */
bool RetryRequest(const FGuid& RequestId); bool RetryRequest(const FGuid& RequestId);
// === RECHERCHE ET CACHE === // === RECHERCHE ===
/** /**
* Chercher une requête en attente correspondant aux critères * Chercher une requête en attente correspondant aux critères
@ -242,28 +233,6 @@ public:
int32 SplitId = -1 int32 SplitId = -1
) const; ) const;
/**
* Récupérer une réponse depuis le cache (données brutes)
*/
bool GetFromCache(
EDTFluxApiDataType RequestType,
FString& OutRawResponse,
int32 ContestId = -1,
int32 StageId = -1,
int32 SplitId = -1
) const;
/**
* Récupérer une réponse parsée depuis le cache
*/
bool GetParsedFromCache(
EDTFluxApiDataType RequestType,
TSharedPtr<FDTFluxServerResponse>& OutResponse,
int32 ContestId = -1,
int32 StageId = -1,
int32 SplitId = -1
) const;
// === ACCESSEURS === // === ACCESSEURS ===
/** /**
@ -294,25 +263,15 @@ public:
struct FRequestStatistics struct FRequestStatistics
{ {
int32 Pending = 0; int32 Pending = 0;
int32 Cached = 0;
int32 Completed = 0; int32 Completed = 0;
int32 Failed = 0; int32 Failed = 0;
int32 TotalRequests = 0; int32 TotalRequests = 0;
int32 CacheHits = 0;
int32 CacheMisses = 0;
float HitRate = 0.0f;
}; };
FRequestStatistics GetStatistics() const; FRequestStatistics GetStatistics() const;
// === NETTOYAGE === // === NETTOYAGE ===
/**
* Nettoyer les entrées de cache expirées
* @return Nombre d'entrées supprimées
*/
int32 CleanupExpiredCache();
/** /**
* Nettoyer les requêtes terminées anciennes * Nettoyer les requêtes terminées anciennes
* @param OlderThanSeconds Supprimer les requêtes plus anciennes que ce délai * @param OlderThanSeconds Supprimer les requêtes plus anciennes que ce délai
@ -321,7 +280,7 @@ public:
int32 CleanupCompletedRequests(float OlderThanSeconds = 300.0f); int32 CleanupCompletedRequests(float OlderThanSeconds = 300.0f);
/** /**
* Vider toutes les requêtes et le cache * Vider toutes les requêtes
*/ */
void ClearAllRequests(); void ClearAllRequests();
@ -353,17 +312,14 @@ private:
// === TIMING POUR LE TICK === // === TIMING POUR LE TICK ===
float TimeSinceLastTimeoutCheck = 0.0f; float TimeSinceLastTimeoutCheck = 0.0f;
float TimeSinceLastCacheCleanup = 0.0f;
float TimeSinceLastRetryCheck = 0.0f; float TimeSinceLastRetryCheck = 0.0f;
static constexpr float TimeoutCheckInterval = 1.0f; static constexpr float TimeoutCheckInterval = 1.0f;
static constexpr float CacheCleanupInterval = 30.0f;
static constexpr float RetryCheckInterval = 0.5f; static constexpr float RetryCheckInterval = 0.5f;
// === STOCKAGE THREAD-SAFE === // === STOCKAGE THREAD-SAFE ===
mutable FCriticalSection RequestsLock; mutable FCriticalSection RequestsLock;
TMap<FGuid, TSharedPtr<FDTFluxTrackedRequest>> AllRequests; TMap<FGuid, TSharedPtr<FDTFluxTrackedRequest>> AllRequests;
TMap<FString, FGuid> CacheKeyToRequestId;
// === CALLBACKS C++ === // === CALLBACKS C++ ===
mutable FCriticalSection CallbacksLock; mutable FCriticalSection CallbacksLock;
@ -373,8 +329,6 @@ private:
// === MÉTRIQUES === // === MÉTRIQUES ===
mutable FCriticalSection MetricsLock; mutable FCriticalSection MetricsLock;
mutable int32 TotalRequests = 0; mutable int32 TotalRequests = 0;
mutable int32 CacheHits = 0;
mutable int32 CacheMisses = 0;
// === PARSER ASYNCHRONE === // === PARSER ASYNCHRONE ===
@ -397,11 +351,6 @@ private:
*/ */
void ProcessRetries(); void ProcessRetries();
/**
* Nettoyer le cache périodiquement
*/
void ProcessCacheCleanup();
/** /**
* Déclencher les callbacks pour une requête * Déclencher les callbacks pour une requête
*/ */
@ -412,17 +361,6 @@ private:
*/ */
void CleanupCallbacks(const FGuid& RequestId); void CleanupCallbacks(const FGuid& RequestId);
/**
* Enregistrer un hit cache dans les métriques
*/
void RecordCacheHit() const;
/**
* Enregistrer un miss cache dans les métriques
*/
void RecordCacheMiss() const;
// === CALLBACKS POUR LE PARSING ASYNCHRONE === // === CALLBACKS POUR LE PARSING ASYNCHRONE ===
/** /**
@ -434,11 +372,4 @@ private:
* Callback appelé quand le parsing asynchrone échoue * Callback appelé quand le parsing asynchrone échoue
*/ */
void OnParsingFailed(const FGuid& RequestId, const FString& ErrorMessage); void OnParsingFailed(const FGuid& RequestId, const FString& ErrorMessage);
// === UTILITAIRES STATIQUES ===
/**
* Générer une clé de cache unique pour une requête
*/
static FString GenerateCacheKey(EDTFluxApiDataType RequestType, int32 ContestId, int32 StageId, int32 SplitId);
}; };

View File

@ -14,17 +14,21 @@ struct DTFLUXNETWORK_API FDTFluxSplitSensorItemResponse
public: public:
UPROPERTY() UPROPERTY()
int Bib; int Bib = -1;
UPROPERTY() UPROPERTY()
FString Type = "split-sensor-item"; FString Type = "split-sensor-item";
UPROPERTY() UPROPERTY()
int ContestID; int ContestID =-1;
UPROPERTY() UPROPERTY()
int StageID; int StageID =-1;
UPROPERTY() UPROPERTY()
int SplitID; int SplitID = -1;
UPROPERTY() UPROPERTY()
FString Time = "-"; FString Time = "-";
UPROPERTY()
int Rank = -1;
UPROPERTY()
FString Gap = "";
}; };

View File

@ -17,20 +17,12 @@ class FDTFluxQueuedRequestManager;
typedef TSharedPtr<FDTFluxWebSocketClient> FDTFluxWebSocketClientSP; typedef TSharedPtr<FDTFluxWebSocketClient> FDTFluxWebSocketClientSP;
// ================================================================================================
// DELEGATES BLUEPRINT POUR LES REQUÊTES TRACKÉES
// ================================================================================================
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(FOnRaceDataReceived, const FDTFluxRaceData& /*RaceDataDefinition*/);
DECLARE_DELEGATE_OneParam(FOnTeamListReceived, const FDTFluxTeamListDefinition& /*TeamListDefinition*/); DECLARE_DELEGATE_OneParam(FOnTeamListReceived, const FDTFluxTeamListDefinition& /*TeamListDefinition*/);
DECLARE_DELEGATE_OneParam(FOnStageRankingReceived, const FDTFluxStageRankings& /*StageRankings*/); DECLARE_DELEGATE_OneParam(FOnStageRankingReceived, const FDTFluxStageRankings& /*StageRankings*/);
@ -42,25 +34,16 @@ DECLARE_DELEGATE_OneParam(FOnTeamStatusUpdateReceived, const FDTFluxTeamStatusUp
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnWebSocketConnected); 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
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
// === ÉTAT DE CONNEXION ===
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Network") UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Network")
EDTFluxConnectionStatus WsStatus = EDTFluxConnectionStatus::Unset; EDTFluxConnectionStatus WsStatus = EDTFluxConnectionStatus::Unset;
// === CONNEXION WEBSOCKET (Legacy) ===
/** /**
* Se connecter au serveur WebSocket * Se connecter au serveur WebSocket
@ -80,8 +63,6 @@ public:
UFUNCTION(BlueprintCallable, Category = "DTFlux|Network") UFUNCTION(BlueprintCallable, Category = "DTFlux|Network")
void Reconnect(); void Reconnect();
// === REQUÊTES TRACKÉES (Nouveau système optimisé) ===
/** /**
* Envoyer une requête trackée avec cache, timeout et retry * Envoyer une requête trackée avec cache, timeout et retry
* @param RequestType Type de requête (ContestRanking, StageRanking, etc.) * @param RequestType Type de requête (ContestRanking, StageRanking, etc.)
@ -125,87 +106,52 @@ public:
FOnDTFluxRequestSuccess& OnSuccess, FOnDTFluxRequestSuccess& OnSuccess,
FOnDTFluxRequestError& OnError, FOnDTFluxRequestError& OnError,
float TimeoutSeconds = 5.0f, float TimeoutSeconds = 5.0f,
int32 MaxRetries = 3, int32 MaxRetries = 3
bool bEnableCache = true
); );
// === ACCESSEURS BLUEPRINT POUR LES REQUÊTES TRACKÉES ===
/**
* Récupérer une requête trackée par son ID
*/
UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests") UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests")
bool GetTrackedRequest(const FGuid& RequestId, FDTFluxTrackedRequest& OutRequest) const; 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") UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests")
bool HasRequestReceivedResponse(const FGuid& RequestId) const; bool HasRequestReceivedResponse(const FGuid& RequestId) const;
/**
* Récupérer les données de réponse d'une requête
*/
UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests") UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests")
FString GetRequestResponseData(const FGuid& RequestId) const; FString GetRequestResponseData(const FGuid& RequestId) const;
/**
* Vérifier si une requête similaire est en attente
*/
UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests") 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;
/**
* Compter le nombre de requêtes en attente
*/
UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests") UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests")
int32 GetPendingRequestCount() const; int32 GetPendingRequestCount() const;
/**
* Récupérer les statistiques du gestionnaire de requêtes
*/
UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests") UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests")
void GetRequestStatistics(int32& OutPending, int32& OutCached, int32& OutCompleted, int32& OutFailed, void GetRequestStatistics(int32& OutPending, int32& OutCompleted, int32& OutFailed) const;
float& OutHitRate) const;
// === REQUÊTES LEGACY (Compatibilité totale) ===
/**
* Envoyer une requête en mode legacy (pour compatibilité)
*/
UFUNCTION(BlueprintCallable, Category = "DTFlux|Legacy") 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);
/**
* Envoyer un message brut via WebSocket
*/
UFUNCTION(BlueprintCallable, Category = "DTFlux|Network") 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") UPROPERTY(BlueprintAssignable, Category = "DTFlux|Network")
FOnWebSocketConnected OnWebSocketConnected; FOnWebSocketConnected OnWebSocketConnected;
/**
* Event déclenché quand une requête trackée se termine avec succès
*/
UPROPERTY(BlueprintAssignable, Category = "DTFlux|Tracked Requests") UPROPERTY(BlueprintAssignable, Category = "DTFlux|Tracked Requests")
FOnDTFluxTrackedRequestCompleted OnTrackedRequestCompleted; FOnDTFluxTrackedRequestCompleted OnTrackedRequestCompleted;
/**
* Event déclenché quand une requête trackée échoue
*/
UPROPERTY(BlueprintAssignable, Category = "DTFlux|Tracked Requests") UPROPERTY(BlueprintAssignable, Category = "DTFlux|Tracked Requests")
FOnDTFluxTrackedRequestFailed OnTrackedRequestFailed; FOnDTFluxTrackedRequestFailed OnTrackedRequestFailed;
// === DELEGATES LEGACY (Compatibilité totale) ===
FOnRaceDataReceived OnRaceDataReceived; FOnRaceDataReceived OnRaceDataReceived;
FOnTeamListReceived OnTeamListReceived; FOnTeamListReceived OnTeamListReceived;
FOnStageRankingReceived OnStageRankingReceived; FOnStageRankingReceived OnStageRankingReceived;
@ -215,7 +161,6 @@ public:
FOnTeamUpdateReceived OnTeamUpdateReceived; FOnTeamUpdateReceived OnTeamUpdateReceived;
FOnTeamStatusUpdateReceived OnTeamStatusUpdateReceived; FOnTeamStatusUpdateReceived OnTeamStatusUpdateReceived;
// Accesseurs pour la compatibilité legacy
FOnRaceDataReceived& OnReceivedRaceData() { return OnRaceDataReceived; } FOnRaceDataReceived& OnReceivedRaceData() { return OnRaceDataReceived; }
FOnTeamListReceived& OnReceivedTeamList() { return OnTeamListReceived; } FOnTeamListReceived& OnReceivedTeamList() { return OnTeamListReceived; }
FOnStageRankingReceived& OnReceivedStageRanking() { return OnStageRankingReceived; } FOnStageRankingReceived& OnReceivedStageRanking() { return OnStageRankingReceived; }
@ -225,45 +170,35 @@ public:
FOnTeamUpdateReceived& OnReceivedTeamUpdate() { return OnTeamUpdateReceived; } FOnTeamUpdateReceived& OnReceivedTeamUpdate() { return OnTeamUpdateReceived; }
FOnTeamStatusUpdateReceived& OnReceivedTeamStatusUpdate() { return OnTeamStatusUpdateReceived; } 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; } TSharedPtr<FDTFluxQueuedRequestManager> GetRequestManager() const { return RequestManager; }
protected: protected:
// === LIFECYCLE DU SUBSYSTEM ===
virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override; virtual void Deinitialize() override;
private: private:
// === CONFIGURATION ===
FDTFluxWsSettings WsSettings; FDTFluxWsSettings WsSettings;
// === CLIENTS RÉSEAU ===
FDTFluxWebSocketClientSP WsClient = nullptr; FDTFluxWebSocketClientSP WsClient = nullptr;
// === REQUEST MANAGER C++ ===
TSharedPtr<FDTFluxQueuedRequestManager> RequestManager; TSharedPtr<FDTFluxQueuedRequestManager> RequestManager;
// === 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 OnWebSocketMessageEvent_Subsystem(const FString& MessageString);
void OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent); 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;
// === PARSING ET TRAITEMENT DES RÉPONSES ===
/** /**
* Essayer de matcher une réponse à une requête trackée * Essayer de matcher une réponse à une requête trackée
@ -294,7 +229,6 @@ private:
void ParseSplitSensorResponse(FDTFluxServerResponse& Response); void ParseSplitSensorResponse(FDTFluxServerResponse& Response);
EDTFluxResponseStatus ProcessPushMessage(FDTFluxServerResponse& Response); EDTFluxResponseStatus ProcessPushMessage(FDTFluxServerResponse& Response);
// === CALLBACKS POUR LE REQUEST MANAGER ===
/** /**
* Callback appelé quand une requête trackée se termine * Callback appelé quand une requête trackée se termine
@ -306,7 +240,6 @@ private:
*/ */
void OnRequestFailed_Internal(const FDTFluxTrackedRequest& FailedRequest); void OnRequestFailed_Internal(const FDTFluxTrackedRequest& FailedRequest);
// === CONFIGURATION DYNAMIQUE ===
/** /**
* Callback appelé quand les paramètres WebSocket changent * Callback appelé quand les paramètres WebSocket changent
@ -319,7 +252,6 @@ private:
*/ */
void ReconnectWs(const FName WsClientId); void ReconnectWs(const FName WsClientId);
// === UTILITAIRES ===
/** /**
* Construire une adresse WebSocket complète * Construire une adresse WebSocket complète

View File

@ -23,7 +23,7 @@ public class DTFluxProjectSettings : ModuleRules
"DeveloperSettings", "DeveloperSettings",
"DTFluxCore", "DTFluxCore",
"Settings", "Settings",
"DeveloperSettings" "DeveloperSettings","AvalancheMedia"
} }
); );

View File

@ -2,6 +2,7 @@
#include "DTFluxGeneralSettings.h" #include "DTFluxGeneralSettings.h"
#include "Assets/DTFluxModelAsset.h"
#include "DTFluxProjectSettingsModule.h" #include "DTFluxProjectSettingsModule.h"
@ -14,3 +15,17 @@ UDTFluxGeneralSettings::UDTFluxGeneralSettings()
UE_LOG(logDTFluxProjectSettings, Log, TEXT("Category Name -> %s"), *GetCategoryName().ToString()); UE_LOG(logDTFluxProjectSettings, Log, TEXT("Category Name -> %s"), *GetCategoryName().ToString());
} }
void UDTFluxGeneralSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
if (PropertyChangedEvent.Property &&
PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UDTFluxGeneralSettings, RemoteTargetRundown))
{
UE_LOG(LogTemp, Log, TEXT("RemoteTargetRundown property changed to: %s"),
RemoteTargetRundown.IsNull() ? TEXT("None") : *RemoteTargetRundown.ToString());
OnRemoteRundownChanged.Broadcast(RemoteTargetRundown);
}
}

View File

@ -3,10 +3,11 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "Assets/DTFluxModelAsset.h"
#include "Engine/DeveloperSettings.h" #include "Engine/DeveloperSettings.h"
#include "DTFluxGeneralSettings.generated.h" #include "DTFluxGeneralSettings.generated.h"
class UAvaRundown;
class UDTFluxModelAsset;
/** /**
* *
*/ */
@ -20,5 +21,14 @@ public:
UDTFluxGeneralSettings(); UDTFluxGeneralSettings();
UPROPERTY(Category="General", Config, EditAnywhere, BlueprintReadOnly, DisplayName="Datastorage File") UPROPERTY(Category="General", Config, EditAnywhere, BlueprintReadOnly, DisplayName="Datastorage File")
TSoftObjectPtr<UDTFluxModelAsset> ModelAsset; TSoftObjectPtr<UDTFluxModelAsset> ModelAsset;
UPROPERTY(Category="General|Remote HTTP", Config, EditAnywhere, BlueprintReadOnly, DisplayName="Rundown Remote Target")
TSoftObjectPtr<UAvaRundown> RemoteTargetRundown;
#if WITH_EDITOR
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
DECLARE_MULTICAST_DELEGATE_OneParam(FOnRemoteRundownChanged, const TSoftObjectPtr<UAvaRundown>& );
FOnRemoteRundownChanged OnRemoteRundownChanged;
#endif
}; };

View File

@ -0,0 +1,31 @@
using UnrealBuildTool;
public class DTFluxRemote : ModuleRules
{
public DTFluxRemote(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"HttpServer",
"JsonUtilities",
"Json",
"DTFluxProjectSettings",
"AvalancheMedia"
}
);
}
}

View File

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

View File

@ -0,0 +1,19 @@
#include "DTFluxRemoteModule.h"
#define LOCTEXT_NAMESPACE "FDTFluxRemoteModule"
DEFINE_LOG_CATEGORY(logDTFluxRemote);
void FDTFluxRemoteModule::StartupModule()
{
}
void FDTFluxRemoteModule::ShutdownModule()
{
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FDTFluxRemoteModule, DTFluxRemote)

View File

@ -0,0 +1,549 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "DTFluxRemoteSubsystem.h"
#include "DTFluxRemoteSubsystem.h"
#include "DTFluxGeneralSettings.h"
#include "DTFluxRemoteModule.h"
#include "DTFluxRemoteModule.h"
#include "HttpServerModule.h"
#include "IHttpRouter.h"
#include "Rundown/AvaRundown.h"
#include "Json.h"
#include "JsonObjectConverter.h"
#include "Engine/Engine.h"
#include "Misc/DateTime.h"
void UDTFluxRemoteSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UE_LOG(logDTFluxRemote, Log, TEXT("DTFlux API Subsystem Initialized"));
#if WITH_EDITOR
// S'abonner aux changements de settings
if (UDTFluxGeneralSettings* Settings = GetMutableDefault<UDTFluxGeneralSettings>())
{
SettingsRundownChangedHandle = Settings->OnRemoteRundownChanged.AddUObject(
this, &UDTFluxRemoteSubsystem::OnSettingsRundownChanged
);
}
#endif
LoadRundownFromSettings();
// Auto-start server (optionnel)
StartHTTPServer(63350);
}
void UDTFluxRemoteSubsystem::Deinitialize()
{
StopHTTPServer();
#if WITH_EDITOR
// Se désabonner du delegate
if (UDTFluxGeneralSettings* Settings = GetMutableDefault<UDTFluxGeneralSettings>())
{
if (SettingsRundownChangedHandle.IsValid())
{
Settings->OnRemoteRundownChanged.Remove(SettingsRundownChangedHandle);
SettingsRundownChangedHandle.Reset();
}
}
#endif
// Décharger proprement le rundown
UnloadCurrentRundown();
UE_LOG(logDTFluxRemote, Log, TEXT("DTFlux API Subsystem Deinitialized"));
Super::Deinitialize();
}
bool UDTFluxRemoteSubsystem::StartHTTPServer(int32 Port)
{
if (bServerRunning)
{
UE_LOG(logDTFluxRemote, Warning, TEXT("HTTP Server already running on port %d"), ServerPort);
return true;
}
ServerPort = Port;
// Get HTTP Server Module
FHttpServerModule& HttpServerModule = FHttpServerModule::Get();
// Create router
HttpRouter = HttpServerModule.GetHttpRouter(ServerPort);
if (!HttpRouter.IsValid())
{
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to create HTTP router for port %d"), ServerPort);
return false;
}
// Setup routes
SetupRoutes();
// Start listening
HttpServerModule.StartAllListeners();
bServerRunning = true;
UE_LOG(logDTFluxRemote, Log, TEXT("DTFlux HTTP API Server started on port %d"), ServerPort);
UE_LOG(logDTFluxRemote, Log, TEXT("Base URL: http://localhost:%d/dtflux/api/v1"), ServerPort);
UE_LOG(logDTFluxRemote, Log, TEXT("Available routes:"));
UE_LOG(logDTFluxRemote, Log, TEXT(" PUT /dtflux/api/v1/title"));
UE_LOG(logDTFluxRemote, Log, TEXT(" PUT /dtflux/api/v1/title-bib"));
UE_LOG(logDTFluxRemote, Log, TEXT(" PUT /dtflux/api/v1/commands"));
return true;
}
void UDTFluxRemoteSubsystem::StopHTTPServer()
{
if (!bServerRunning)
{
return;
}
// Remove route handlers
if (HttpRouter.IsValid())
{
HttpRouter->UnbindRoute(TitleRouteHandle);
HttpRouter->UnbindRoute(TitleBibRouteHandle);
HttpRouter->UnbindRoute(CommandsRouteHandle);
}
// Stop server
FHttpServerModule& HttpServerModule = FHttpServerModule::Get();
HttpServerModule.StopAllListeners();
HttpRouter.Reset();
bServerRunning = false;
UE_LOG(logDTFluxRemote, Log, TEXT("DTFlux HTTP API Server stopped"));
}
bool UDTFluxRemoteSubsystem::IsHTTPServerRunning() const
{
return bServerRunning;
}
void UDTFluxRemoteSubsystem::ResetPendingTitleData()
{
bHasPendingTitleRequest = false;
}
void UDTFluxRemoteSubsystem::ResetPendingBibData()
{
bHasPendingTitleBibRequest = false;
}
void UDTFluxRemoteSubsystem::SetupRoutes()
{
if (!HttpRouter.IsValid())
{
return;
}
// Route: POST /dtflux/api/v1/title
TitleRouteHandle = HttpRouter->BindRoute(
FHttpPath(TEXT("/dtflux/api/v1/title")),
EHttpServerRequestVerbs::VERB_PUT,
FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleTitleRequest)
);
// Route: POST /dtflux/api/v1/title-bib
TitleBibRouteHandle = HttpRouter->BindRoute(
FHttpPath(TEXT("/dtflux/api/v1/title-bib")),
EHttpServerRequestVerbs::VERB_PUT,
FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleTitleBibRequest)
);
// Route: POST /dtflux/api/v1/commands
CommandsRouteHandle = HttpRouter->BindRoute(
FHttpPath(TEXT("/dtflux/api/v1/commands")),
EHttpServerRequestVerbs::VERB_PUT,
FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleCommandsRequest)
);
UE_LOG(logDTFluxRemote, Log, TEXT("HTTP routes configured successfully"));
}
bool UDTFluxRemoteSubsystem::HandleTitleRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete)
{
UE_LOG(logDTFluxRemote, Log, TEXT("Received Title request"));
// Parse JSON
TSharedPtr<FJsonObject> JsonObject = ParseJsonFromRequest(Request);
if (!JsonObject.IsValid())
{
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid JSON")), TEXT("application/json")));
return true;
}
// Parse title data
FDTFluxRemoteTitleData TitleData;
if (!ParseTitleData(JsonObject, TitleData))
{
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid title data format")), TEXT("application/json")));
return true;
}
// Broadcast event (execute on Game Thread)
AsyncTask(ENamedThreads::GameThread, [this, TitleData]()
{
OnTitleReceived.Broadcast(TitleData);
if (RemotedRundown && RemotedRundown->IsValidLowLevel())
{
PendingTitleData = TitleData;
bHasPendingTitleRequest = true;
UE_LOG(logDTFluxRemote, Log, TEXT("Playing page %i"), TitleData.RundownPageId);
RemotedRundown->PlayPage(TitleData.RundownPageId, EAvaRundownPagePlayType::PlayFromStart);
}
else
{
UE_LOG(logDTFluxRemote, Warning, TEXT("No rundown loaded"));
}
});
OnComplete(FHttpServerResponse::Create(CreateSuccessResponse(TEXT("Title data received")), TEXT("application/json")));
return true;
}
bool UDTFluxRemoteSubsystem::HandleTitleBibRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete)
{
UE_LOG(logDTFluxRemote, Log, TEXT("Received Title-Bib request"));
TSharedPtr<FJsonObject> JsonObject = ParseJsonFromRequest(Request);
if (!JsonObject.IsValid())
{
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid JSON")), TEXT("application/json")));
return true;
}
FDTFluxRemoteBibData BibData;
if (!ParseTitleBibData(JsonObject, BibData))
{
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid title-bib data format")), TEXT("application/json")));
return true;
}
AsyncTask(ENamedThreads::GameThread, [this, BibData]()
{
OnTitleBibReceived.Broadcast(BibData);
});
OnComplete(FHttpServerResponse::Create(CreateSuccessResponse(TEXT("Title-bib data received")), TEXT("application/json")));
return true;
}
bool UDTFluxRemoteSubsystem::HandleCommandsRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete)
{
UE_LOG(logDTFluxRemote, Log, TEXT("Received Commands request"));
TSharedPtr<FJsonObject> JsonObject = ParseJsonFromRequest(Request);
if (!JsonObject.IsValid())
{
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid JSON")), TEXT("application/json")));
return true;
}
FDTFluxRemoteCommandData CommandData;
if (!ParseCommandData(JsonObject, CommandData))
{
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid command data format")), TEXT("application/json")));
return true;
}
AsyncTask(ENamedThreads::GameThread, [this, CommandData]()
{
OnCommandReceived.Broadcast(CommandData);
if (RemotedRundown && RemotedRundown->IsValidLowLevel())
{
RemotedRundown->StopPage(CommandData.RundownPageId, EAvaRundownPageStopOptions::None, false);
UE_LOG(logDTFluxRemote, Log, TEXT("Stoping page %i"), CommandData.RundownPageId);
}
else
{
UE_LOG(logDTFluxRemote, Warning, TEXT("No rundown loaded"));
}
});
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("OK")), TEXT("application/json")));
return true;
}
TSharedPtr<FJsonObject> UDTFluxRemoteSubsystem::ParseJsonFromRequest(const FHttpServerRequest& Request)
{
// Get request body
TArray<uint8> Body = Request.Body;
FString JsonString;
if (Body.Num() > 0)
{
// Ajouter un null terminator si nécessaire
if (Body.Last() != 0)
{
Body.Add(0);
}
JsonString = FString(UTF8_TO_TCHAR(reinterpret_cast<const char*>(Body.GetData())));
}
UE_LOG(logDTFluxRemote, Verbose, TEXT("Received JSON: %s"), *JsonString);
// Parse JSON
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
{
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to parse JSON: %s"), *JsonString);
return nullptr;
}
return JsonObject;
}
FString UDTFluxRemoteSubsystem::CreateSuccessResponse(const FString& Message)
{
TSharedPtr<FJsonObject> ResponseObject = MakeShareable(new FJsonObject);
ResponseObject->SetBoolField(TEXT("success"), true);
ResponseObject->SetStringField(TEXT("message"), Message);
ResponseObject->SetStringField(TEXT("timestamp"), FDateTime::Now().ToIso8601());
FString OutputString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
FJsonSerializer::Serialize(ResponseObject.ToSharedRef(), Writer);
return OutputString;
}
FString UDTFluxRemoteSubsystem::CreateErrorResponse(const FString& Error, int32 Code)
{
TSharedPtr<FJsonObject> ResponseObject = MakeShareable(new FJsonObject);
ResponseObject->SetBoolField(TEXT("success"), false);
ResponseObject->SetStringField(TEXT("error"), Error);
ResponseObject->SetNumberField(TEXT("code"), Code);
ResponseObject->SetStringField(TEXT("timestamp"), FDateTime::Now().ToIso8601());
FString OutputString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
FJsonSerializer::Serialize(ResponseObject.ToSharedRef(), Writer);
return OutputString;
}
bool UDTFluxRemoteSubsystem::ParseTitleData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteTitleData& OutData)
{
if (!JsonObject.IsValid())
{
UE_LOG(logDTFluxRemote, Error, TEXT("Invalid JSON object for %s"), TEXT("FDTFluxRemoteTitleData"));
return false;
}
if (FJsonObjectConverter::JsonObjectToUStruct<FDTFluxRemoteTitleData>(JsonObject.ToSharedRef(), &OutData))
{
UE_LOG(logDTFluxRemote, Log, TEXT("Successfully parsed %s"), TEXT("FDTFluxRemoteTitleData"));
return true;
}
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to convert JSON to %s struct"),TEXT("FDTFluxRemoteTitleData"));
return false;
}
bool UDTFluxRemoteSubsystem::ParseTitleBibData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteBibData& OutData)
{
if (!JsonObject.IsValid())
{
UE_LOG(logDTFluxRemote, Error, TEXT("Invalid JSON object for %s"), TEXT("FDTFluxRemoteBibData"));
return false;
}
if (FJsonObjectConverter::JsonObjectToUStruct<FDTFluxRemoteBibData>(JsonObject.ToSharedRef(), &OutData))
{
UE_LOG(logDTFluxRemote, Log, TEXT("Successfully parsed %s"), TEXT("FDTFluxRemoteBibData"));
return true;
}
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to convert JSON to %s struct"),TEXT("FDTFluxRemoteBibData"));
return false;
}
bool UDTFluxRemoteSubsystem::ParseCommandData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteCommandData& OutData)
{
if (!JsonObject.IsValid())
{
UE_LOG(logDTFluxRemote, Error, TEXT("Invalid JSON object for %s"), TEXT("FDTFluxRemoteCommandData"));
return false;
}
if (FJsonObjectConverter::JsonObjectToUStruct<FDTFluxRemoteCommandData>(JsonObject.ToSharedRef(), &OutData))
{
UE_LOG(logDTFluxRemote, Log, TEXT("Successfully parsed %s"), TEXT("FDTFluxRemoteCommandData"));
return true;
}
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to convert JSON to %s struct"),TEXT("FDTFluxRemoteCommandData"));
return false;
}
void UDTFluxRemoteSubsystem::UnloadCurrentRundown()
{
if (RemotedRundown)
{
UE_LOG(logDTFluxRemote, Log, TEXT("Unloading current rundown"));
// Ici vous pouvez ajouter une logique de nettoyage si nécessaire
// Par exemple : RemotedRundown->StopAllPages();
RemotedRundown = nullptr;
}
}
void UDTFluxRemoteSubsystem::LoadRundownFromSettings()
{
const UDTFluxGeneralSettings* Settings = GetDefault<UDTFluxGeneralSettings>();
if (!Settings)
{
UE_LOG(logDTFluxRemote, Warning, TEXT("Cannot access DTFlux settings"));
return;
}
TSoftObjectPtr<UAvaRundown> RundownAsset = Settings->RemoteTargetRundown;
if (RundownAsset.IsNull())
{
UE_LOG(logDTFluxRemote, Log, TEXT("No rundown specified in settings"));
UnloadCurrentRundown();
return;
}
if (RemotedRundown && RemotedRundown == RundownAsset.LoadSynchronous())
{
UE_LOG(logDTFluxRemote, Log, TEXT("Rundown already loaded: %s"), *RundownAsset.ToString());
return;
}
// Décharger l'ancien rundown d'abord
UnloadCurrentRundown();
RundownAsset = RundownAsset.LoadSynchronous();
// Charger le nouveau rundown
if ( RundownAsset.IsValid())
{
UE_LOG(logDTFluxRemote, Log, TEXT("Successfully loaded rundown from settings: %s"), *RundownAsset.ToString());
}
else
{
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to load rundown from settings: %s"), *RundownAsset.ToString());
}
LoadRundown(RundownAsset);
}
bool UDTFluxRemoteSubsystem::LoadRundown(const TSoftObjectPtr<UAvaRundown>& RundownAsset)
{
if (RundownAsset.IsNull())
{
UE_LOG(logDTFluxRemote, Warning, TEXT("Cannot load rundown: asset is null"));
UnloadCurrentRundown();
return false;
}
// Charger le rundown de manière synchrone
UAvaRundown* LoadedRundown = RundownAsset.LoadSynchronous();
// Vérifier si le rundown est déjà chargé
if (RemotedRundown && RemotedRundown == LoadedRundown)
{
UE_LOG(logDTFluxRemote, Log, TEXT("Rundown already loaded: %s"), *RundownAsset.ToString());
return true;
}
// Décharger l'ancien rundown d'abord
UnloadCurrentRundown();
// Assigner le nouveau rundown
RemotedRundown = LoadedRundown;
// Vérifier que le chargement a réussi
if (RemotedRundown && RemotedRundown->IsValidLowLevel())
{
RemotedRundown->InitializePlaybackContext();
UE_LOG(logDTFluxRemote, Log, TEXT("Successfully loaded rundown: %s"), *RundownAsset.ToString());
return true;
}
else
{
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to load rundown: %s"), *RundownAsset.ToString());
RemotedRundown = nullptr;
return false;
}
}
#if WITH_EDITOR
void UDTFluxRemoteSubsystem::OnSettingsRundownChanged(const TSoftObjectPtr<UAvaRundown>& NewRundown)
{
}
#endif
// Manual processing functions for testing
bool UDTFluxRemoteSubsystem::ProcessTitleData(const FString& JsonString)
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
{
return false;
}
FDTFluxRemoteTitleData TitleData;
if (ParseTitleData(JsonObject, TitleData))
{
OnTitleReceived.Broadcast(TitleData);
return true;
}
return false;
}
bool UDTFluxRemoteSubsystem::ProcessTitleBibData(const FString& JsonString)
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
{
return false;
}
FDTFluxRemoteBibData TitleBibData;
if (ParseTitleBibData(JsonObject, TitleBibData))
{
OnTitleBibReceived.Broadcast(TitleBibData);
return true;
}
return false;
}
bool UDTFluxRemoteSubsystem::ProcessCommandData(const FString& JsonString)
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
{
return false;
}
FDTFluxRemoteCommandData CommandData;
if (ParseCommandData(JsonObject, CommandData))
{
OnCommandReceived.Broadcast(CommandData);
return true;
}
return false;
}

View File

@ -0,0 +1,220 @@
// DTFluxRemoteActor.cpp
#include "DTFluxRemotedLevelController.h"
#include "DTFluxRemoteSubsystem.h"
#include "Engine/Engine.h"
#include "Engine/World.h"
ADTFluxRemotedLevelController::ADTFluxRemotedLevelController()
{
PrimaryActorTick.bCanEverTick = false;
RemoteSubsystem = nullptr;
bEventsBound = false;
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Constructor called"));
}
void ADTFluxRemotedLevelController::PostInitializeComponents()
{
Super::PostInitializeComponents();
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: PostInitializeComponents called"));
// Essayer de bind dès que possible
InitializeSubsystemBinding();
}
void ADTFluxRemotedLevelController::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: BeginPlay called"));
// S'assurer que le binding est fait (au cas où PostInitializeComponents aurait échoué)
if (!bEventsBound)
{
InitializeSubsystemBinding();
}
}
void ADTFluxRemotedLevelController::InitializeSubsystemBinding()
{
// Éviter le double binding
if (bEventsBound)
{
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Events already bound, skipping"));
return;
}
// Récupérer le subsystem
if (UWorld* World = GetWorld())
{
RemoteSubsystem = GEngine->GetEngineSubsystem<UDTFluxRemoteSubsystem>();
if (RemoteSubsystem)
{
// Bind les events du subsystem
RemoteSubsystem->OnTitleReceived.AddDynamic(
this, &ADTFluxRemotedLevelController::OnTitleDataReceived
);
RemoteSubsystem->OnTitleBibReceived.AddDynamic(
this, &ADTFluxRemotedLevelController::OnTitleBibDataReceived
);
RemoteSubsystem->OnCommandReceived.AddDynamic(
this, &ADTFluxRemotedLevelController::OnCommandDataReceived
);
bEventsBound = true;
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Successfully bound to subsystem events"));
}
else
{
UE_LOG(LogTemp, Warning, TEXT("DTFluxRemoteActor: DTFluxRemoteSubsystem not available yet"));
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("DTFluxRemoteActor: World not available yet"));
}
}
void ADTFluxRemotedLevelController::EnsureSubsystemBinding()
{
if (!bEventsBound)
{
InitializeSubsystemBinding();
}
}
void ADTFluxRemotedLevelController::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
// Unbind les events pour éviter les fuites mémoire
if (RemoteSubsystem && bEventsBound)
{
if (TitleReceivedHandle.IsValid())
{
RemoteSubsystem->OnTitleReceived.RemoveDynamic(this, &ADTFluxRemotedLevelController::OnTitleDataReceived);
TitleReceivedHandle.Reset();
}
if (TitleBibReceivedHandle.IsValid())
{
RemoteSubsystem->OnTitleBibReceived.RemoveDynamic(this, &ADTFluxRemotedLevelController::OnTitleBibDataReceived);
TitleBibReceivedHandle.Reset();
}
if (CommandReceivedHandle.IsValid())
{
RemoteSubsystem->OnCommandReceived.RemoveDynamic(this, &ADTFluxRemotedLevelController::OnCommandDataReceived);
CommandReceivedHandle.Reset();
}
bEventsBound = false;
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Unbound from subsystem events"));
}
Super::EndPlay(EndPlayReason);
}
void ADTFluxRemotedLevelController::OnTitleDataReceived(const FDTFluxRemoteTitleData& TitleData)
{
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Received Title Data - %s %s (RundownPageId: %d)"),
*TitleData.FirstName, *TitleData.LastName, TitleData.RundownPageId);
// Broadcast l'event Blueprint
OnTitleReceived.Broadcast(TitleData);
// Appeler l'event Blueprint implémentable
BP_OnTitleDataReceived(TitleData);
// Appeler la fonction virtuelle C++ (peut être overridée dans les classes dérivées)
HandleTitleData(TitleData);
}
void ADTFluxRemotedLevelController::OnTitleBibDataReceived(const FDTFluxRemoteBibData& BibData)
{
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Received Title Bib Data - Bib: %d"), BibData.Bib);
// Broadcast l'event Blueprint
OnTitleBibReceived.Broadcast(BibData);
// Appeler l'event Blueprint implémentable
BP_OnTitleBibDataReceived(BibData);
// Appeler la fonction virtuelle C++ (peut être overridée dans les classes dérivées)
HandleTitleBibData(BibData);
}
void ADTFluxRemotedLevelController::OnCommandDataReceived(const FDTFluxRemoteCommandData& CommandData)
{
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Received Command Data - Type: %s, : RundownPageId %i"),
*CommandData.Type, CommandData.RundownPageId);
// Broadcast l'event Blueprint
OnCommandReceived.Broadcast(CommandData);
// Appeler l'event Blueprint implémentable
BP_OnCommandDataReceived(CommandData);
// Appeler la fonction virtuelle C++ (peut être overridée dans les classes dérivées)
HandleCommandData(CommandData);
}
bool ADTFluxRemotedLevelController::IsSubsystemAvailable() const
{
return RemoteSubsystem && RemoteSubsystem->IsValidLowLevel();
}
bool ADTFluxRemotedLevelController::IsHTTPServerRunning() const
{
if (RemoteSubsystem)
{
return RemoteSubsystem->IsHTTPServerRunning();
}
return false;
}
void ADTFluxRemotedLevelController::StartHTTPServer(int32 Port)
{
if (RemoteSubsystem)
{
RemoteSubsystem->StartHTTPServer(Port);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("DTFluxRemoteActor: Cannot start HTTP server - subsystem not available"));
}
}
void ADTFluxRemotedLevelController::StopHTTPServer()
{
if (RemoteSubsystem)
{
RemoteSubsystem->StopHTTPServer();
}
else
{
UE_LOG(LogTemp, Warning, TEXT("DTFluxRemoteActor: Cannot stop HTTP server - subsystem not available"));
}
}
// Implémentations par défaut des fonctions virtuelles C++
void ADTFluxRemotedLevelController::HandleTitleData_Implementation(const FDTFluxRemoteTitleData& TitleData)
{
// Implémentation par défaut - peut être overridée dans les classes dérivées
UE_LOG(LogTemp, Verbose, TEXT("DTFluxRemoteActor: Handling Title Data (default implementation)"));
}
void ADTFluxRemotedLevelController::HandleTitleBibData_Implementation(const FDTFluxRemoteBibData& BibData)
{
// Implémentation par défaut - peut être overridée dans les classes dérivées
UE_LOG(LogTemp, Verbose, TEXT("DTFluxRemoteActor: Handling Title Bib Data (default implementation)"));
}
void ADTFluxRemotedLevelController::HandleCommandData_Implementation(const FDTFluxRemoteCommandData& CommandData)
{
// Implémentation par défaut - peut être overridée dans les classes dérivées
UE_LOG(LogTemp, Verbose, TEXT("DTFluxRemoteActor: Handling Command Data (default implementation)"));
}

View File

@ -0,0 +1,76 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "DTFluxRemoteMessage.generated.h"
USTRUCT(BlueprintType)
struct FDTFluxRemoteBasicData
{
GENERATED_BODY()
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
FDateTime UpdateAt = FDateTime::Now();
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
int RundownPageId = -1;
FDTFluxRemoteBasicData() = default;
FDTFluxRemoteBasicData(const FDateTime& InUpdateAt): UpdateAt(InUpdateAt){};
};
USTRUCT(BlueprintType)
struct FDTFluxRemoteTitleData : public FDTFluxRemoteBasicData
{
GENERATED_BODY()
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
FString FirstName = "";
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
FString LastName = "";
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
FString Function1 = "";
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
FString Function2 = "";
FDTFluxRemoteTitleData() = default;
FDTFluxRemoteTitleData(const FString InFirstName, const FString InLastName, const FString InFunction1, const FString InFunction2):
FirstName(InFirstName), LastName(InLastName), Function1(InFunction1), Function2(InFunction2){};
};
USTRUCT(BlueprintType)
struct FDTFluxRemoteBibData : public FDTFluxRemoteBasicData
{
GENERATED_BODY()
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
int Bib = -1;
FDTFluxRemoteBibData() = default;
FDTFluxRemoteBibData(int InBib): Bib(InBib){};
};
USTRUCT(BlueprintType)
struct FDTFluxRemoteCommandData : public FDTFluxRemoteBasicData
{
GENERATED_BODY()
public:
FDTFluxRemoteCommandData() = default;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
FString Type = "Stop";
FDTFluxRemoteCommandData(FString InType):
Type(InType){};
};

View File

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

View File

@ -0,0 +1,117 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/EngineSubsystem.h"
#include "DTFluxRemoteMessage.h"
#include "HttpRouteHandle.h"
#include "IHttpRouter.h"
#include "DTFluxRemoteSubsystem.generated.h"
class UAvaRundown;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTitleReceived, const FDTFluxRemoteTitleData&, TitleData);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTitleBibReceived, const FDTFluxRemoteBibData&, TitleBibData);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCommandReceived, const FDTFluxRemoteCommandData&, CommandData);
/**
*
*/
UCLASS(BlueprintType, Category="DTFlux|Remote")
class DTFLUXREMOTE_API UDTFluxRemoteSubsystem : public UEngineSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
UPROPERTY(BlueprintAssignable, Category = "DTFlux API")
FOnTitleReceived OnTitleReceived;
UPROPERTY(BlueprintAssignable, Category = "DTFlux API")
FOnTitleBibReceived OnTitleBibReceived;
UPROPERTY(BlueprintAssignable, Category = "DTFlux API")
FOnCommandReceived OnCommandReceived;
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
bool StartHTTPServer(int32 Port = 63350);
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
void StopHTTPServer();
UFUNCTION(BlueprintCallable, Category = "DTFlux API", BlueprintPure)
bool IsHTTPServerRunning() const;
UFUNCTION(BlueprintCallable, Category = "DTFlux API", BlueprintPure)
int32 GetServerPort() const { return ServerPort; }
// Manual data processing (for testing)
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
bool ProcessTitleData(const FString& JsonString);
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
bool ProcessTitleBibData(const FString& JsonString);
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
bool ProcessCommandData(const FString& JsonString);
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "DTFlux API")
bool bHasPendingTitleRequest = false;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "DTFlux API")
bool bHasPendingTitleBibRequest = false;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "DTFlux API")
FDTFluxRemoteTitleData PendingTitleData;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "DTFlux API")
FDTFluxRemoteBibData PendingTitleBibData;
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
void ResetPendingTitleData();
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
void ResetPendingBibData();
private:
void SetupRoutes();
bool HandleTitleRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete);
bool HandleTitleBibRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete);
bool HandleCommandsRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete);
TSharedPtr<FJsonObject> ParseJsonFromRequest(const FHttpServerRequest& Request);
FString CreateSuccessResponse(const FString& Message = TEXT("Success"));
FString CreateErrorResponse(const FString& Error, int32 Code = 400);
bool ParseTitleData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteTitleData& OutData);
bool ParseTitleBibData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteBibData& OutData);
bool ParseCommandData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteCommandData& OutData);
private:
TSharedPtr<IHttpRouter> HttpRouter;
TSoftObjectPtr<UAvaRundown> RemotedRundown;
int32 ServerPort = 63350;
bool bServerRunning = false;
FHttpRouteHandle TitleRouteHandle;
FHttpRouteHandle TitleBibRouteHandle;
FHttpRouteHandle CommandsRouteHandle;
void UnloadCurrentRundown();
void LoadRundownFromSettings();
bool LoadRundown(const TSoftObjectPtr<UAvaRundown>& RundownAsset);
#if WITH_EDITOR
FDelegateHandle SettingsRundownChangedHandle;
#endif
#if WITH_EDITOR
// Callback pour les changements de settings
UFUNCTION()
void OnSettingsRundownChanged(const TSoftObjectPtr<UAvaRundown>& NewRundown);
#endif
};

View File

@ -0,0 +1,94 @@
// DTFluxRemotedLevelController.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "DTFluxRemoteSubsystem.h"
#include "DTFluxRemotedLevelController.generated.h"
UCLASS(BlueprintType, Blueprintable)
class DTFLUXREMOTE_API ADTFluxRemotedLevelController : public AActor
{
GENERATED_BODY()
public:
ADTFluxRemotedLevelController();
protected:
virtual void PostInitializeComponents() override;
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
// Subsystem et binding
UPROPERTY(BlueprintReadOnly, Category = "DTFlux")
UDTFluxRemoteSubsystem* RemoteSubsystem;
FDelegateHandle TitleReceivedHandle;
FDelegateHandle TitleBibReceivedHandle;
FDelegateHandle CommandReceivedHandle;
bool bEventsBound;
// Fonctions de binding
void InitializeSubsystemBinding();
// ✅ CORRECTION : Callbacks avec UFUNCTION()
UFUNCTION()
void OnTitleDataReceived(const FDTFluxRemoteTitleData& TitleData);
UFUNCTION()
void OnTitleBibDataReceived(const FDTFluxRemoteBibData& BibData);
UFUNCTION()
void OnCommandDataReceived(const FDTFluxRemoteCommandData& CommandData);
public:
// Events Blueprint-friendly
UPROPERTY(BlueprintAssignable, Category = "DTFlux Events")
FOnTitleReceived OnTitleReceived;
UPROPERTY(BlueprintAssignable, Category = "DTFlux Events")
FOnTitleBibReceived OnTitleBibReceived;
UPROPERTY(BlueprintAssignable, Category = "DTFlux Events")
FOnCommandReceived OnCommandReceived;
// Fonctions utilitaires
UFUNCTION(BlueprintCallable, Category = "DTFlux")
bool IsSubsystemAvailable() const;
UFUNCTION(BlueprintCallable, Category = "DTFlux")
bool IsHTTPServerRunning() const;
UFUNCTION(BlueprintCallable, Category = "DTFlux")
void StartHTTPServer(int32 Port = 63350);
UFUNCTION(BlueprintCallable, Category = "DTFlux")
void StopHTTPServer();
UFUNCTION(BlueprintCallable, Category = "DTFlux")
void EnsureSubsystemBinding();
protected:
// Events Blueprint implémentables
UFUNCTION(BlueprintImplementableEvent, Category = "DTFlux Events")
void BP_OnTitleDataReceived(const FDTFluxRemoteTitleData& TitleData);
UFUNCTION(BlueprintImplementableEvent, Category = "DTFlux Events")
void BP_OnTitleBibDataReceived(const FDTFluxRemoteBibData& BibData);
UFUNCTION(BlueprintImplementableEvent, Category = "DTFlux Events")
void BP_OnCommandDataReceived(const FDTFluxRemoteCommandData& CommandData);
// Fonctions virtuelles C++
UFUNCTION(BlueprintNativeEvent, Category = "DTFlux Events")
void HandleTitleData(const FDTFluxRemoteTitleData& TitleData);
virtual void HandleTitleData_Implementation(const FDTFluxRemoteTitleData& TitleData);
UFUNCTION(BlueprintNativeEvent, Category = "DTFlux Events")
void HandleTitleBibData(const FDTFluxRemoteBibData& BibData);
virtual void HandleTitleBibData_Implementation(const FDTFluxRemoteBibData& BibData);
UFUNCTION(BlueprintNativeEvent, Category = "DTFlux Events")
void HandleCommandData(const FDTFluxRemoteCommandData& CommandData);
virtual void HandleCommandData_Implementation(const FDTFluxRemoteCommandData& CommandData);
};

View File

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

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

@ -0,0 +1,67 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "DTFluxUtils.h"
#include "DTFluxCoreSubsystem.h"
#include "DTFluxUtilitiesModule.h"
FText UFTDFluxUtils::GetFormatedName(const int& Bib, const int MaxChar, const FString Separator,
const FString OverFlowChar)
{
UDTFluxCoreSubsystem* CoreSubsystem = GEngine->GetEngineSubsystem<UDTFluxCoreSubsystem>();
if (CoreSubsystem != nullptr)
{
FDTFluxParticipant OutParticipant;
CoreSubsystem->GetParticipant(Bib, OutParticipant);
return OutParticipant.GetFormattedNameText(MaxChar, Separator, OverFlowChar);
}
UE_LOG(logDTFluxUtilities, Error, TEXT("DTFluxCoreSubsystem not available"));
return FText();
}
FText UFTDFluxUtils::GetParticipantFormatedName(FDTFluxParticipant& Participant, const int MaxChar,
const FString Separator,
const FString OverFlowChar)
{
return Participant.GetFormattedNameText(MaxChar, Separator, OverFlowChar);
}
void UFTDFluxUtils::GetFullName(const int Bib, FText& OutFullName)
{
OutFullName = FText();
UDTFluxCoreSubsystem* CoreSubsystem = GEngine->GetEngineSubsystem<UDTFluxCoreSubsystem>();
if(CoreSubsystem != nullptr)
{
FDTFluxParticipant Participant;
if(CoreSubsystem->GetParticipant(Bib, Participant))
{
FString FormattedName = "";
if (Participant.IsTeam())
{
OutFullName = FText::FromString(Participant.Team);
return;
}
if (Participant.GetTeammate().IsEmpty())
{
UE_LOG(logDTFluxUtilities, Warning, TEXT("Non teammate found with Bib %i"), Bib)
return;
}
OutFullName = FText::FromString(FString::Printf(TEXT("%s %s"), *Participant.GetTeammate()[0].FirstName,
*Participant.GetTeammate()[0].LastName));
return;
}
UE_LOG(logDTFluxUtilities, Warning, TEXT("Participant not found with Bib %i"), Bib);
}
UE_LOG(logDTFluxUtilities, Error, TEXT("DTFluxCoreSubsystem not available"));
}
TArray<FDTFluxSplitSensorInfo> UFTDFluxUtils::SortSplitRankingsByRank(const TArray<FDTFluxSplitSensorInfo>& Rankings)
{
TArray<FDTFluxSplitSensorInfo> CopyRankings = Rankings;
CopyRankings.Sort([](const FDTFluxSplitSensorInfo& A, const FDTFluxSplitSensorInfo& B)
{
return A.Rank < B.Rank;
});
return CopyRankings;
}

View File

@ -1,19 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "FTDFluxUtils.h"
#include "DTFluxCoreSubsystem.h"
FText UFTDFluxUtils::GetFormatedName(const int& Bib, const int MaxChar, const FString OverFlowChar)
{
UDTFluxCoreSubsystem* CoreSubsystem = GEngine->GetEngineSubsystem<UDTFluxCoreSubsystem>();
const FDTFluxParticipant OutParticipant = CoreSubsystem->GetParticipant(Bib);
return OutParticipant.GetFormattedNameText(MaxChar, OverFlowChar);
}
FText UFTDFluxUtils::GetParticipantFormatedName(FDTFluxParticipant& Participant, const int MaxChar,
const FString OverFlowChar)
{
return Participant.GetFormattedNameText(MaxChar, OverFlowChar);
}

View File

@ -0,0 +1,114 @@
// ===============================================
// 3. SOURCE FILE (.CPP) - TOUS LES INCLUDES
// ===============================================
// YourRundownController.cpp
#include "RundownController.h"
#include "DTFluxUtilitiesModule.h"
#include "Rundown/AvaRundown.h"
#include "Rundown/AvaRundownPage.h"
#include "Engine/Engine.h"
#include "Engine/World.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/ConstructorHelpers.h"
#include "TimerManager.h"
#include "Logging/LogMacros.h"
#include "Components/StaticMeshComponent.h"
#include "Materials/MaterialInterface.h"
#include "Blueprint/UserWidget.h"
DEFINE_LOG_CATEGORY_STATIC(LogYourRundownController, Log, All);
// ===============================================
// 4. IMPLÉMENTATION SIMPLE
// ===============================================
ARundownController::ARundownController()
{
PrimaryActorTick.bCanEverTick = false;
}
bool ARundownController::LoadRundown(const TSoftObjectPtr<UAvaRundown> RundownAsset)
{
if (RundownAsset.IsNull())
{
UE_LOG(logDTFluxUtilities, Error, TEXT("RundownAsset Null"));
return false;
}
// Charger l'asset rundown
CurrentRundown = RundownAsset.LoadSynchronous();
if (!CurrentRundown)
{
UE_LOG(LogYourRundownController, Error, TEXT("Failed to load rundown: %s"), *RundownAsset.ToString());
return false;
}
// Initialiser le contexte de playback
CurrentRundown->InitializePlaybackContext();
UE_LOG(LogYourRundownController, Log, TEXT("Successfully loaded rundown: %s"), *RundownAsset.ToString());
return true;
}
bool ARundownController::PlayPage(int32 PageId)
{
if (!CurrentRundown)
{
UE_LOG(LogYourRundownController, Error, TEXT("No rundown loaded. Call LoadRundown first."));
return false;
}
// Vérifier que la page existe
const FAvaRundownPage& Page = CurrentRundown->GetPage(PageId);
if (!Page.IsValidPage())
{
UE_LOG(LogYourRundownController, Error, TEXT("Invalid page ID: %d"), PageId);
return false;
}
// Jouer la page
bool bSuccess = CurrentRundown->PlayPage(PageId, EAvaRundownPagePlayType::PlayFromStart);
if (bSuccess)
{
CurrentPageId = PageId;
UE_LOG(LogYourRundownController, Log, TEXT("Playing page %d: %s"), PageId, *Page.GetPageName());
}
else
{
UE_LOG(LogYourRundownController, Warning, TEXT("Failed to play page %d"), PageId);
}
return bSuccess;
}
bool ARundownController::StopPage(int32 PageId)
{
if (!CurrentRundown)
{
UE_LOG(LogYourRundownController, Error, TEXT("No rundown loaded"));
return false;
}
bool bSuccess = CurrentRundown->StopPage(PageId, EAvaRundownPageStopOptions::Default, false);
if (bSuccess)
{
UE_LOG(LogYourRundownController, Log, TEXT("Stopped page %d"), PageId);
}
return bSuccess;
}
FString ARundownController::ListePages()
{
if (!CurrentRundown)
{
UE_LOG(logDTFluxUtilities, Error, TEXT("No rundown loaded"));
return FString();
}
return FString();
}

View File

@ -1,17 +1,14 @@
// 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:
DTFluxDatesUtilities(); DTFluxDatesUtilities();
~DTFluxDatesUtilities(); ~DTFluxDatesUtilities();
static bool CompileDateAndTime(const FString& Time, const FString& Date, FDateTime& OutDateTime); static bool CompileDateAndTime(const FString& Time, const FString& Date, FDateTime& OutDateTime);
static bool CompileDateAndTime(const FDateTime& Time, const FDateTime& Date, FDateTime& OutDateTime); static bool CompileDateAndTime(const FDateTime& Time, const FDateTime& Date, FDateTime& OutDateTime);
}; };

View File

@ -0,0 +1,77 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "DTFluxCore/Public/Types/Struct/DTFluxTeamListStruct.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Types/Struct/DTFluxSplitSensor.h"
#include "DTFluxUtils.generated.h"
/**
*
*/
UCLASS()
class DTFLUXUTILITIES_API UFTDFluxUtils : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="name, concat, participant"))
static FText GetFormatedName(const int& Bib, const int MaxChar = 10, const FString Separator = ".",
const FString OverFlowChar = "...");
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="name, concat, participant"))
static FText GetParticipantFormatedName(FDTFluxParticipant& Participant, const int MaxChar = 10,
const FString Separator = ".",
const FString OverFlowChar = "...");
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="convert, StageRankings, DTFlux"))
static void CastToDTFluxStageRanking(const FDTFluxDetailedRankingItem& ItemRanking, FDTFluxStageRanking& OutRanking)
{
CastRankingItem<FDTFluxStageRanking>(ItemRanking, OutRanking);
}
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="convert, StageRankings, DTFlux"))
static void CastToDTFluxStageRankingArray(const TArray<FDTFluxDetailedRankingItem>& ItemRanking,
TArray<FDTFluxStageRanking>& OutRanking)
{
CastRankingArray<FDTFluxStageRanking>(ItemRanking, OutRanking);
}
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="convert, StageRankings, DTFlux"))
static void CastToDTFluxSplitRanking(const FDTFluxDetailedRankingItem& ItemRanking, FDTFluxSplitRanking& OutRanking)
{
CastRankingItem<FDTFluxSplitRanking>(ItemRanking, OutRanking);
}
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="convert, StageRankings, DTFlux"))
static void CastToDTFluxSplitRankingArray(const TArray<FDTFluxDetailedRankingItem>& ItemRanking,
TArray<FDTFluxSplitRanking>& OutRanking)
{
CastRankingArray<FDTFluxSplitRanking>(ItemRanking, OutRanking);
}
template <typename T>
static void CastRankingItem(const FDTFluxDetailedRankingItem& ItemRanking, T& OutRanking)
{
OutRanking = static_cast<T>(ItemRanking);
}
template <typename T>
static void CastRankingArray(const TArray<FDTFluxDetailedRankingItem>& ItemRanking, TArray<T>& OutRanking)
{
OutRanking.Empty();
for (auto& Item : ItemRanking)
{
OutRanking.Add(static_cast<T>(Item));
}
}
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils")
static void GetFullName(const int Bib, FText& OutFullName);
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils")
static TArray<FDTFluxSplitSensorInfo> SortSplitRankingsByRank(const TArray<FDTFluxSplitSensorInfo>& Rankings);
};

View File

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

View File

@ -0,0 +1,41 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Engine/World.h"
#include "TimerManager.h"
#include "Rundown/AvaRundown.h"
#include "UObject/SoftObjectPath.h"
#include "UObject/ObjectMacros.h"
#include "RundownController.generated.h"
UCLASS(BlueprintType, Blueprintable)
class DTFLUXUTILITIES_API ARundownController : public AActor
{
GENERATED_BODY()
public:
ARundownController();
UFUNCTION(BlueprintCallable, Category = "DTFlux")
bool LoadRundown(const TSoftObjectPtr<UAvaRundown> RundownAsset);
UFUNCTION(BlueprintCallable, Category = "DTFlux")
bool PlayPage(int32 PageId);
UFUNCTION(BlueprintCallable, Category = "DTFlux")
bool StopPage(int32 PageId);
UFUNCTION(BlueprintCallable, Category = "DTFlux")
FString ListePages();
protected:
UPROPERTY(BlueprintReadOnly, Category = "DTFlux")
TObjectPtr<UAvaRundown> CurrentRundown;
UPROPERTY(BlueprintReadWrite, Category = "DTFlux")
int32 CurrentPageId = 1;
};