Compare commits

10 Commits

11 changed files with 615 additions and 88 deletions

View File

@ -393,15 +393,22 @@ void UDTFluxCoreSubsystem::InitParticipantTracking(const int Bib, const int Cont
// get all splits // get all splits
TArray<FDTFluxSplitSensorInfo> SplitSensorInfos; TArray<FDTFluxSplitSensorInfo> SplitSensorInfos;
FDTFluxSplitSensorKey SplitSensorKey; FDTFluxSplitSensorKey SplitSensorKey;
SplitSensorKey.ContestId = ContestId;
SplitSensorKey.StageId = StageId;
SplitSensorKey.Bib = Bib;
for (auto Split : Contest.Splits) for (auto Split : Contest.Splits)
{ {
SplitSensorKey = FDTFluxSplitSensorKey();
SplitSensorKey.ContestId = ContestId;
SplitSensorKey.StageId = StageId;
SplitSensorKey.Bib = Bib;
SplitSensorKey.SplitId = Split.SplitId; SplitSensorKey.SplitId = Split.SplitId;
if (DataStorage->SplitSensorInfoCache.Contains(SplitSensorKey)) if (DataStorage->SplitSensorInfoCache.Contains(SplitSensorKey))
{ {
SplitSensorInfos.Add(DataStorage->SplitSensorInfoCache[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 else
{ {
@ -410,17 +417,11 @@ void UDTFluxCoreSubsystem::InitParticipantTracking(const int Bib, const int Cont
} }
FDTFluxSplitHistory History; FDTFluxSplitHistory History;
History.SplitSensors = SplitSensorInfos; History.SplitSensors = SplitSensorInfos;
OnParticipantTrackingReady.Broadcast(History); if (GetParticipant(Bib, History.Participant))
{
OnParticipantTrackingReady.Broadcast(History);
}
} }
FDTFluxSplitHistory SplitHistory;
if (GetParticipant(Bib, SplitHistory.Participant))
{
}
FString Text = "sqfhds";
FName Key = FName(Text);
} }
FGuid UDTFluxCoreSubsystem::InitContestRankingsDisplay(const int ContestId) FGuid UDTFluxCoreSubsystem::InitContestRankingsDisplay(const int ContestId)

View File

@ -28,12 +28,14 @@ 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()
} }
} }
} }
@ -46,25 +48,6 @@ void UDTFluxPursuitManager::SetPursuitInfoIsMassStart(FDTFluxPursuitGroup& NextF
} }
} }
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);
}
void UDTFluxPursuitManager::GetPursuit(TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext, void UDTFluxPursuitManager::GetPursuit(TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext,
TArray<FDTFluxPursuitInfo>& OutPursuitNext, bool& BIsFocusTruncate, TArray<FDTFluxPursuitInfo>& OutPursuitNext, bool& BIsFocusTruncate,
@ -73,9 +56,9 @@ void UDTFluxPursuitManager::GetPursuit(TArray<FDTFluxPursuitInfo>& OutPursuitFoc
FDateTime MetricsStartFunction = FDateTime::UtcNow(); FDateTime MetricsStartFunction = FDateTime::UtcNow();
FDateTime CurrentTime = FDateTime::Now(); FDateTime CurrentTime = FDateTime::Now();
// UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("=== GetPursuit CALLED ===")); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("=== GetPursuit CALLED ==="));
// UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("MaxSimultaneousPursuit: %d"), MaxSimultaneousPursuit); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("MaxSimultaneousPursuit: %d"), MaxSimultaneousPursuit);
// UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Available groups: %d"), GroupedPursuit.Num()); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Available groups: %d"), GroupedPursuit.Num());
// BAd Parameter // BAd Parameter
if (MaxSimultaneousPursuit <= 0) if (MaxSimultaneousPursuit <= 0)
@ -111,19 +94,16 @@ void UDTFluxPursuitManager::GetPursuit(TArray<FDTFluxPursuitInfo>& OutPursuitFoc
GroupedPursuit.RemoveAt(i); GroupedPursuit.RemoveAt(i);
} }
} }
OutPursuitFocusNext.Reset(); OutPursuitFocusNext.Reset();
OutPursuitNext.Reset(); OutPursuitNext.Reset();
// === VÉRIFICATION CRITIQUE : S'assurer qu'il reste des groupes après suppression ===
if (GroupedPursuit.IsEmpty()) if (GroupedPursuit.IsEmpty())
{ {
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("All groups were expired and removed - no groups available")); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("All groups were expired and removed - no groups available"));
OutPursuitFocusNext.Reset(); OutPursuitFocusNext.Reset();
OutPursuitNext.Reset(); OutPursuitNext.Reset();
BIsFocusTruncate = false; BIsFocusTruncate = false;
bIsSequenceDone = true; // Marquer la séquence comme terminée bIsSequenceDone = true;
return; return;
} }
@ -180,7 +160,6 @@ void UDTFluxPursuitManager::GetPursuit(TArray<FDTFluxPursuitInfo>& OutPursuitFoc
} }
} }
// === LOGS DE RÉSUMÉ ===
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("=== PURSUIT RESULTS ===")); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("=== PURSUIT RESULTS ==="));
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Focus: %d participants"), OutPursuitFocusNext.Num()); 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("Next: %d participants"), OutPursuitNext.Num());
@ -322,7 +301,6 @@ bool UDTFluxPursuitManager::LaunchPursuitSequence()
{ {
if (Pair.Value.StartTimeGlobal != FDateTime::MinValue() && Pair.Value.StartTimeGlobal != FDateTime::MaxValue()) if (Pair.Value.StartTimeGlobal != FDateTime::MinValue() && Pair.Value.StartTimeGlobal != FDateTime::MaxValue())
{ {
// récuperation de la ref de la valeur actuel de la fréquence dans la TMap Freq
int& CurrentFreq = StartTimeFrequency.FindOrAdd(Pair.Value.StartTimeGlobal, 0); int& CurrentFreq = StartTimeFrequency.FindOrAdd(Pair.Value.StartTimeGlobal, 0);
CurrentFreq = Pair.Value.PursuitGroup.Num(); CurrentFreq = Pair.Value.PursuitGroup.Num();
if (CurrentFreq > MaxFrequency) if (CurrentFreq > MaxFrequency)
@ -348,3 +326,25 @@ bool UDTFluxPursuitManager::LaunchPursuitSequence()
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

@ -3,12 +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 UAvaRundown;
// class UDTFluxModelAsset; class UDTFluxModelAsset;
/** /**
* *
*/ */

View File

@ -23,6 +23,8 @@ public class DTFluxRemote : ModuleRules
"HttpServer", "HttpServer",
"JsonUtilities", "JsonUtilities",
"Json", "Json",
"DTFluxProjectSettings",
"AvalancheMedia"
} }
); );
} }

View File

@ -3,11 +3,15 @@
#include "DTFluxRemoteSubsystem.h" #include "DTFluxRemoteSubsystem.h"
#include "DTFluxRemoteSubsystem.h" #include "DTFluxRemoteSubsystem.h"
#include "DTFluxGeneralSettings.h"
#include "DTFluxRemoteModule.h" #include "DTFluxRemoteModule.h"
#include "DTFluxRemoteModule.h" #include "DTFluxRemoteModule.h"
#include "HttpServerModule.h" #include "HttpServerModule.h"
#include "IHttpRouter.h" #include "IHttpRouter.h"
#include "Rundown/AvaRundown.h"
#include "Json.h" #include "Json.h"
#include "JsonObjectConverter.h"
#include "Engine/Engine.h" #include "Engine/Engine.h"
#include "Misc/DateTime.h" #include "Misc/DateTime.h"
@ -18,6 +22,16 @@ void UDTFluxRemoteSubsystem::Initialize(FSubsystemCollectionBase& Collection)
UE_LOG(logDTFluxRemote, Log, TEXT("DTFlux API Subsystem Initialized")); 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) // Auto-start server (optionnel)
StartHTTPServer(63350); StartHTTPServer(63350);
} }
@ -26,6 +40,21 @@ void UDTFluxRemoteSubsystem::Deinitialize()
{ {
StopHTTPServer(); 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")); UE_LOG(logDTFluxRemote, Log, TEXT("DTFlux API Subsystem Deinitialized"));
Super::Deinitialize(); Super::Deinitialize();
@ -64,9 +93,9 @@ bool UDTFluxRemoteSubsystem::StartHTTPServer(int32 Port)
UE_LOG(logDTFluxRemote, Log, TEXT("DTFlux HTTP API Server started on port %d"), ServerPort); 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("Base URL: http://localhost:%d/dtflux/api/v1"), ServerPort);
UE_LOG(logDTFluxRemote, Log, TEXT("Available routes:")); UE_LOG(logDTFluxRemote, Log, TEXT("Available routes:"));
UE_LOG(logDTFluxRemote, Log, TEXT(" POST /dtflux/api/v1/title")); UE_LOG(logDTFluxRemote, Log, TEXT(" PUT /dtflux/api/v1/title"));
UE_LOG(logDTFluxRemote, Log, TEXT(" POST /dtflux/api/v1/title-bib")); UE_LOG(logDTFluxRemote, Log, TEXT(" PUT /dtflux/api/v1/title-bib"));
UE_LOG(logDTFluxRemote, Log, TEXT(" POST /dtflux/api/v1/commands")); UE_LOG(logDTFluxRemote, Log, TEXT(" PUT /dtflux/api/v1/commands"));
return true; return true;
} }
@ -101,6 +130,16 @@ bool UDTFluxRemoteSubsystem::IsHTTPServerRunning() const
return bServerRunning; return bServerRunning;
} }
void UDTFluxRemoteSubsystem::ResetPendingTitleData()
{
bHasPendingTitleRequest = false;
}
void UDTFluxRemoteSubsystem::ResetPendingBibData()
{
bHasPendingTitleBibRequest = false;
}
void UDTFluxRemoteSubsystem::SetupRoutes() void UDTFluxRemoteSubsystem::SetupRoutes()
{ {
if (!HttpRouter.IsValid()) if (!HttpRouter.IsValid())
@ -111,21 +150,21 @@ void UDTFluxRemoteSubsystem::SetupRoutes()
// Route: POST /dtflux/api/v1/title // Route: POST /dtflux/api/v1/title
TitleRouteHandle = HttpRouter->BindRoute( TitleRouteHandle = HttpRouter->BindRoute(
FHttpPath(TEXT("/dtflux/api/v1/title")), FHttpPath(TEXT("/dtflux/api/v1/title")),
EHttpServerRequestVerbs::VERB_GET, EHttpServerRequestVerbs::VERB_PUT,
FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleTitleRequest) FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleTitleRequest)
); );
// Route: POST /dtflux/api/v1/title-bib // Route: POST /dtflux/api/v1/title-bib
TitleBibRouteHandle = HttpRouter->BindRoute( TitleBibRouteHandle = HttpRouter->BindRoute(
FHttpPath(TEXT("/dtflux/api/v1/title-bib")), FHttpPath(TEXT("/dtflux/api/v1/title-bib")),
EHttpServerRequestVerbs::VERB_GET, EHttpServerRequestVerbs::VERB_PUT,
FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleTitleBibRequest) FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleTitleBibRequest)
); );
// Route: POST /dtflux/api/v1/commands // Route: POST /dtflux/api/v1/commands
CommandsRouteHandle = HttpRouter->BindRoute( CommandsRouteHandle = HttpRouter->BindRoute(
FHttpPath(TEXT("/dtflux/api/v1/commands")), FHttpPath(TEXT("/dtflux/api/v1/commands")),
EHttpServerRequestVerbs::VERB_GET, EHttpServerRequestVerbs::VERB_PUT,
FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleCommandsRequest) FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleCommandsRequest)
); );
@ -156,9 +195,19 @@ bool UDTFluxRemoteSubsystem::HandleTitleRequest(const FHttpServerRequest& Reques
AsyncTask(ENamedThreads::GameThread, [this, TitleData]() AsyncTask(ENamedThreads::GameThread, [this, TitleData]()
{ {
OnTitleReceived.Broadcast(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"));
}
}); });
// Send success response
OnComplete(FHttpServerResponse::Create(CreateSuccessResponse(TEXT("Title data received")), TEXT("application/json"))); OnComplete(FHttpServerResponse::Create(CreateSuccessResponse(TEXT("Title data received")), TEXT("application/json")));
return true; return true;
} }
@ -211,9 +260,18 @@ bool UDTFluxRemoteSubsystem::HandleCommandsRequest(const FHttpServerRequest& Req
AsyncTask(ENamedThreads::GameThread, [this, CommandData]() AsyncTask(ENamedThreads::GameThread, [this, CommandData]()
{ {
OnCommandReceived.Broadcast(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(CreateSuccessResponse(TEXT("Command data received")), TEXT("application/json"))); OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("OK")), TEXT("application/json")));
return true; return true;
} }
@ -221,7 +279,17 @@ TSharedPtr<FJsonObject> UDTFluxRemoteSubsystem::ParseJsonFromRequest(const FHttp
{ {
// Get request body // Get request body
TArray<uint8> Body = Request.Body; TArray<uint8> Body = Request.Body;
FString JsonString = FString(UTF8_TO_TCHAR(reinterpret_cast<const char*>(Body.GetData()))); 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); UE_LOG(logDTFluxRemote, Verbose, TEXT("Received JSON: %s"), *JsonString);
@ -271,51 +339,153 @@ bool UDTFluxRemoteSubsystem::ParseTitleData(const TSharedPtr<FJsonObject>& JsonO
{ {
if (!JsonObject.IsValid()) if (!JsonObject.IsValid())
{ {
UE_LOG(logDTFluxRemote, Error, TEXT("Invalid JSON object for %s"), TEXT("FDTFluxRemoteTitleData"));
return false; return false;
} }
// Parse title fields if (FJsonObjectConverter::JsonObjectToUStruct<FDTFluxRemoteTitleData>(JsonObject.ToSharedRef(), &OutData))
JsonObject->TryGetStringField(TEXT("LastName"), OutData.LastName); {
JsonObject->TryGetStringField(TEXT("FirsName"), OutData.FirstName); UE_LOG(logDTFluxRemote, Log, TEXT("Successfully parsed %s"), TEXT("FDTFluxRemoteTitleData"));
JsonObject->TryGetStringField(TEXT("Function1"), OutData.Function1); return true;
JsonObject->TryGetStringField(TEXT("Function2"), OutData.Function2); }
UE_LOG(logDTFluxRemote, Log, TEXT("Parsed Title Data - LastName: %s, FirstName: %s"), *OutData.LastName, *OutData.FirstName); UE_LOG(logDTFluxRemote, Error, TEXT("Failed to convert JSON to %s struct"),TEXT("FDTFluxRemoteTitleData"));
return true; return false;
} }
bool UDTFluxRemoteSubsystem::ParseTitleBibData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteBibData& OutData) bool UDTFluxRemoteSubsystem::ParseTitleBibData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteBibData& OutData)
{ {
if (!JsonObject.IsValid()) if (!JsonObject.IsValid())
{ {
UE_LOG(logDTFluxRemote, Error, TEXT("Invalid JSON object for %s"), TEXT("FDTFluxRemoteBibData"));
return false; return false;
} }
JsonObject->TryGetNumberField(TEXT("Bib"), OutData.Bib); 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;
UE_LOG(logDTFluxRemote, Log, TEXT("Parsed Title-Bib Data - Bib: %i"), OutData.Bib);
return true;
} }
bool UDTFluxRemoteSubsystem::ParseCommandData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteCommandData& OutData) bool UDTFluxRemoteSubsystem::ParseCommandData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteCommandData& OutData)
{ {
if (!JsonObject.IsValid()) if (!JsonObject.IsValid())
{ {
UE_LOG(logDTFluxRemote, Error, TEXT("Invalid JSON object for %s"), TEXT("FDTFluxRemoteCommandData"));
return false; return false;
} }
JsonObject->TryGetNumberField(TEXT("type"), OutData.Type); if (FJsonObjectConverter::JsonObjectToUStruct<FDTFluxRemoteCommandData>(JsonObject.ToSharedRef(), &OutData))
JsonObject->TryGetStringField(TEXT("Data"), OutData.Data); {
UE_LOG(logDTFluxRemote, Log, TEXT("Successfully parsed %s"), TEXT("FDTFluxRemoteCommandData"));
return true;
}
UE_LOG(logDTFluxRemote, Log, TEXT("Parsed Command Data - Command Type: %i, Data: %s"), UE_LOG(logDTFluxRemote, Error, TEXT("Failed to convert JSON to %s struct"),TEXT("FDTFluxRemoteCommandData"));
OutData.Type, *OutData.Data); return false;
return true;
} }
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 // Manual processing functions for testing
bool UDTFluxRemoteSubsystem::ProcessTitleData(const FString& JsonString) bool UDTFluxRemoteSubsystem::ProcessTitleData(const FString& JsonString)
{ {
@ -374,3 +544,6 @@ bool UDTFluxRemoteSubsystem::ProcessCommandData(const FString& JsonString)
return false; 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

@ -14,6 +14,8 @@ public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote") UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
FDateTime UpdateAt = FDateTime::Now(); FDateTime UpdateAt = FDateTime::Now();
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
int RundownPageId = -1;
FDTFluxRemoteBasicData() = default; FDTFluxRemoteBasicData() = default;
FDTFluxRemoteBasicData(const FDateTime& InUpdateAt): UpdateAt(InUpdateAt){}; FDTFluxRemoteBasicData(const FDateTime& InUpdateAt): UpdateAt(InUpdateAt){};
}; };
@ -38,6 +40,7 @@ public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote") UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
FString Function2 = ""; FString Function2 = "";
FDTFluxRemoteTitleData() = default; FDTFluxRemoteTitleData() = default;
FDTFluxRemoteTitleData(const FString InFirstName, const FString InLastName, const FString InFunction1, const FString InFunction2): FDTFluxRemoteTitleData(const FString InFirstName, const FString InLastName, const FString InFunction1, const FString InFunction2):
FirstName(InFirstName), LastName(InLastName), Function1(InFunction1), Function2(InFunction2){}; FirstName(InFirstName), LastName(InLastName), Function1(InFunction1), Function2(InFunction2){};
@ -64,12 +67,10 @@ public:
FDTFluxRemoteCommandData() = default; FDTFluxRemoteCommandData() = default;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote") UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
int Type = -1; FString Type = "Stop";
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
FString Data = "";
FDTFluxRemoteCommandData(int InType, FString InData): FDTFluxRemoteCommandData(FString InType):
Type(InType), Data(InData){}; Type(InType){};
}; };

View File

@ -9,6 +9,7 @@
#include "IHttpRouter.h" #include "IHttpRouter.h"
#include "DTFluxRemoteSubsystem.generated.h" #include "DTFluxRemoteSubsystem.generated.h"
class UAvaRundown;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTitleReceived, const FDTFluxRemoteTitleData&, TitleData); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTitleReceived, const FDTFluxRemoteTitleData&, TitleData);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTitleBibReceived, const FDTFluxRemoteBibData&, TitleBibData); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTitleBibReceived, const FDTFluxRemoteBibData&, TitleBibData);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCommandReceived, const FDTFluxRemoteCommandData&, CommandData); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCommandReceived, const FDTFluxRemoteCommandData&, CommandData);
@ -21,7 +22,6 @@ class DTFLUXREMOTE_API UDTFluxRemoteSubsystem : public UEngineSubsystem
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override; virtual void Deinitialize() override;
@ -56,6 +56,25 @@ public:
UFUNCTION(BlueprintCallable, Category = "DTFlux API") UFUNCTION(BlueprintCallable, Category = "DTFlux API")
bool ProcessCommandData(const FString& JsonString); 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: private:
void SetupRoutes(); void SetupRoutes();
@ -73,10 +92,26 @@ private:
private: private:
TSharedPtr<IHttpRouter> HttpRouter; TSharedPtr<IHttpRouter> HttpRouter;
TSoftObjectPtr<UAvaRundown> RemotedRundown;
int32 ServerPort = 63350; int32 ServerPort = 63350;
bool bServerRunning = false; bool bServerRunning = false;
FHttpRouteHandle TitleRouteHandle; FHttpRouteHandle TitleRouteHandle;
FHttpRouteHandle TitleBibRouteHandle; FHttpRouteHandle TitleBibRouteHandle;
FHttpRouteHandle CommandsRouteHandle; 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

@ -56,10 +56,12 @@ void UFTDFluxUtils::GetFullName(const int Bib, FText& OutFullName)
UE_LOG(logDTFluxUtilities, Error, TEXT("DTFluxCoreSubsystem not available")); UE_LOG(logDTFluxUtilities, Error, TEXT("DTFluxCoreSubsystem not available"));
} }
void UFTDFluxUtils::SortSplitRankingsByRank(TArray<FDTFluxSplitRanking>& Rankings) TArray<FDTFluxSplitSensorInfo> UFTDFluxUtils::SortSplitRankingsByRank(const TArray<FDTFluxSplitSensorInfo>& Rankings)
{ {
Rankings.Sort([](const FDTFluxSplitRanking& A, const FDTFluxSplitRanking& B) TArray<FDTFluxSplitSensorInfo> CopyRankings = Rankings;
CopyRankings.Sort([](const FDTFluxSplitSensorInfo& A, const FDTFluxSplitSensorInfo& B)
{ {
return A.Rank < B.Rank; return A.Rank < B.Rank;
}); });
return CopyRankings;
} }

View File

@ -5,7 +5,7 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "DTFluxCore/Public/Types/Struct/DTFluxTeamListStruct.h" #include "DTFluxCore/Public/Types/Struct/DTFluxTeamListStruct.h"
#include "Kismet/BlueprintFunctionLibrary.h" #include "Kismet/BlueprintFunctionLibrary.h"
#include "Types/Struct/DTFluxRankingStructs.h" #include "Types/Struct/DTFluxSplitSensor.h"
#include "DTFluxUtils.generated.h" #include "DTFluxUtils.generated.h"
/** /**
@ -71,7 +71,7 @@ public:
static void GetFullName(const int Bib, FText& OutFullName); static void GetFullName(const int Bib, FText& OutFullName);
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils") UFUNCTION(BlueprintCallable, Category="DTFlux|Utils")
static void SortSplitRankingsByRank(TArray<FDTFluxSplitRanking>& Rankings); static TArray<FDTFluxSplitSensorInfo> SortSplitRankingsByRank(const TArray<FDTFluxSplitSensorInfo>& Rankings);
}; };