Compare commits
13 Commits
d6b8874827
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 892e282d1d | |||
| adc46114d8 | |||
| c88718fe35 | |||
| 8fe2750a54 | |||
| 33d3ea1e37 | |||
| 0bc765cd29 | |||
| dbe904e388 | |||
| 1f35fcbcf5 | |||
| 65c32d9240 | |||
| 609f623737 | |||
| e02ed8538f | |||
| 9b85bfc94a | |||
| da89e35eb2 |
@ -49,6 +49,11 @@
|
||||
"Name": "DTFluxAPIStatus",
|
||||
"Type": "Editor",
|
||||
"LoadingPhase": "Default"
|
||||
},
|
||||
{
|
||||
"Name": "DTFluxRemote",
|
||||
"Type": "Editor",
|
||||
"LoadingPhase": "Default"
|
||||
}
|
||||
],
|
||||
"Plugins": [
|
||||
|
||||
@ -393,15 +393,22 @@ void UDTFluxCoreSubsystem::InitParticipantTracking(const int Bib, const int Cont
|
||||
// get all splits
|
||||
TArray<FDTFluxSplitSensorInfo> SplitSensorInfos;
|
||||
FDTFluxSplitSensorKey SplitSensorKey;
|
||||
SplitSensorKey.ContestId = ContestId;
|
||||
SplitSensorKey.StageId = StageId;
|
||||
SplitSensorKey.Bib = Bib;
|
||||
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))
|
||||
{
|
||||
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
|
||||
{
|
||||
@ -410,17 +417,11 @@ void UDTFluxCoreSubsystem::InitParticipantTracking(const int Bib, const int Cont
|
||||
}
|
||||
FDTFluxSplitHistory History;
|
||||
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)
|
||||
@ -432,6 +433,7 @@ FGuid UDTFluxCoreSubsystem::InitContestRankingsDisplay(const int ContestId)
|
||||
// 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;
|
||||
@ -548,7 +550,7 @@ FGuid UDTFluxCoreSubsystem::InitSplitRankingsDisplay(const int ContestId, const
|
||||
EDTFluxApiDataType::SplitRanking, ContestId, StageId, SplitId, OnSuccess, OnError, true);
|
||||
return DisplayRequestId;
|
||||
}
|
||||
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DTFluxDatastorage unavailable ..."));
|
||||
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DTFluxDataStorage unavailable ..."));
|
||||
OnSplitRankingDisplayReady.Broadcast(FGuid(), false);
|
||||
return FGuid();
|
||||
}
|
||||
@ -607,25 +609,7 @@ bool UDTFluxCoreSubsystem::GetSplitRankingForBib(const int ContestId, const int
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UDTFluxCoreSubsystem::GetContestRanking(const int ContestId, FDTFluxContestRanking& OutContestRanking)
|
||||
{
|
||||
if (DataStorage)
|
||||
{
|
||||
FDTFluxContest Contest;
|
||||
if (GetContestForId(ContestId, Contest))
|
||||
{
|
||||
for (auto& Ranking : DataStorage->ContestRankings[ContestId].Rankings)
|
||||
{
|
||||
OutContestRanking = Ranking;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Unable to find ContestRanking for ContestId %i"), ContestId);
|
||||
return false;
|
||||
}
|
||||
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage not available"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool UDTFluxCoreSubsystem::GetContestRankings(const int ContestId,
|
||||
FDTFluxContestRankings& OutContestRankings)
|
||||
@ -638,8 +622,9 @@ bool UDTFluxCoreSubsystem::GetContestRankings(const int ContestId,
|
||||
if (NetworkSubsystem)
|
||||
{
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Requesting ContestRankings for ContestId %i"), ContestId);
|
||||
TArray<int> TackedContestIds = {ContestId};
|
||||
TrackedRequestContestRankings(TackedContestIds);
|
||||
TArray<int> TrackedContestIds;
|
||||
TrackedContestIds.Add(ContestId);
|
||||
TrackedRequestContestRankings(TrackedContestIds);
|
||||
return false;
|
||||
}
|
||||
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("NetworkSubsystem unavailable"));
|
||||
|
||||
@ -28,12 +28,14 @@ void UDTFluxPursuitManager::InitPursuit(const TArray<int> InContestIds, const in
|
||||
FDTFluxContest Contest;
|
||||
if (CoreSubsystem->GetContestForId(ContestId, Contest))
|
||||
{
|
||||
BindRankings();
|
||||
// BindRankings();
|
||||
FDTFluxStageKey StageKey = FDTFluxStageKey(ContestId, Contest.GetLastStageId());
|
||||
FDTFluxStageRankings TempStageRankings;
|
||||
//Obtenir les ranking Frais.
|
||||
CoreSubsystem->GetStageRankingsWithKey(StageKey, TempStageRankings, false);
|
||||
PendingStageRanking.Add(StageKey, false);
|
||||
CoreSubsystem->GetStageRankingsWithKey(StageKey, TempStageRankings, true);
|
||||
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,
|
||||
TArray<FDTFluxPursuitInfo>& OutPursuitNext, bool& BIsFocusTruncate,
|
||||
@ -73,9 +56,9 @@ void UDTFluxPursuitManager::GetPursuit(TArray<FDTFluxPursuitInfo>& OutPursuitFoc
|
||||
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());
|
||||
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)
|
||||
@ -111,19 +94,16 @@ void UDTFluxPursuitManager::GetPursuit(TArray<FDTFluxPursuitInfo>& OutPursuitFoc
|
||||
GroupedPursuit.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OutPursuitFocusNext.Reset();
|
||||
OutPursuitNext.Reset();
|
||||
|
||||
// === VÉRIFICATION CRITIQUE : S'assurer qu'il reste des groupes après suppression ===
|
||||
if (GroupedPursuit.IsEmpty())
|
||||
{
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("All groups were expired and removed - no groups available"));
|
||||
OutPursuitFocusNext.Reset();
|
||||
OutPursuitNext.Reset();
|
||||
BIsFocusTruncate = false;
|
||||
bIsSequenceDone = true; // Marquer la séquence comme terminée
|
||||
bIsSequenceDone = true;
|
||||
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("Focus: %d participants"), OutPursuitFocusNext.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())
|
||||
{
|
||||
// récuperation de la ref de la valeur actuel de la fréquence dans la TMap Freq
|
||||
int& CurrentFreq = StartTimeFrequency.FindOrAdd(Pair.Value.StartTimeGlobal, 0);
|
||||
CurrentFreq = Pair.Value.PursuitGroup.Num();
|
||||
if (CurrentFreq > MaxFrequency)
|
||||
@ -348,3 +326,25 @@ bool UDTFluxPursuitManager::LaunchPursuitSequence()
|
||||
OnPursuitSequenceReady.Broadcast(PursuitData);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void UDTFluxPursuitManager::DebugFocusNext(const TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext)
|
||||
{
|
||||
FString FocusBibs;
|
||||
for (const auto& Pursuit : OutPursuitFocusNext)
|
||||
{
|
||||
FocusBibs += FString::Printf(TEXT("%d "), Pursuit.Bib);
|
||||
}
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Focus Bibs: %s"), *FocusBibs);
|
||||
}
|
||||
|
||||
void UDTFluxPursuitManager::DebugOutPoursuitNext(const TArray<FDTFluxPursuitInfo>& OutPursuitNext)
|
||||
{
|
||||
FString NextBibs;
|
||||
for (int32 i = 0; i < OutPursuitNext.Num(); i++)
|
||||
{
|
||||
NextBibs += FString::Printf(TEXT("%d "), OutPursuitNext[i].Bib);
|
||||
}
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Next Bibs: %s"), *NextBibs);
|
||||
}
|
||||
@ -122,15 +122,14 @@ public:
|
||||
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 GetContestRanking(const int ContestId, FDTFluxContestRanking& OutContestRanking);
|
||||
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||
bool GetContestRankings(const int ContestId, FDTFluxContestRankings& OutContestRankings);
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||
|
||||
@ -23,7 +23,7 @@ public class DTFluxProjectSettings : ModuleRules
|
||||
"DeveloperSettings",
|
||||
"DTFluxCore",
|
||||
"Settings",
|
||||
"DeveloperSettings"
|
||||
"DeveloperSettings","AvalancheMedia"
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
#include "DTFluxGeneralSettings.h"
|
||||
#include "Assets/DTFluxModelAsset.h"
|
||||
|
||||
#include "DTFluxProjectSettingsModule.h"
|
||||
|
||||
@ -14,3 +15,17 @@ UDTFluxGeneralSettings::UDTFluxGeneralSettings()
|
||||
UE_LOG(logDTFluxProjectSettings, Log, TEXT("Category Name -> %s"), *GetCategoryName().ToString());
|
||||
|
||||
}
|
||||
|
||||
void UDTFluxGeneralSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
||||
{
|
||||
Super::PostEditChangeProperty(PropertyChangedEvent);
|
||||
if (PropertyChangedEvent.Property &&
|
||||
PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UDTFluxGeneralSettings, RemoteTargetRundown))
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("RemoteTargetRundown property changed to: %s"),
|
||||
RemoteTargetRundown.IsNull() ? TEXT("None") : *RemoteTargetRundown.ToString());
|
||||
|
||||
OnRemoteRundownChanged.Broadcast(RemoteTargetRundown);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -3,10 +3,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Assets/DTFluxModelAsset.h"
|
||||
#include "Engine/DeveloperSettings.h"
|
||||
#include "DTFluxGeneralSettings.generated.h"
|
||||
|
||||
class UAvaRundown;
|
||||
class UDTFluxModelAsset;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ -20,5 +21,14 @@ public:
|
||||
UDTFluxGeneralSettings();
|
||||
UPROPERTY(Category="General", Config, EditAnywhere, BlueprintReadOnly, DisplayName="Datastorage File")
|
||||
TSoftObjectPtr<UDTFluxModelAsset> ModelAsset;
|
||||
UPROPERTY(Category="General|Remote HTTP", Config, EditAnywhere, BlueprintReadOnly, DisplayName="Rundown Remote Target")
|
||||
TSoftObjectPtr<UAvaRundown> RemoteTargetRundown;
|
||||
|
||||
#if WITH_EDITOR
|
||||
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||
|
||||
DECLARE_MULTICAST_DELEGATE_OneParam(FOnRemoteRundownChanged, const TSoftObjectPtr<UAvaRundown>& );
|
||||
FOnRemoteRundownChanged OnRemoteRundownChanged;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
31
Source/DTFluxRemote/DTFluxRemote.Build.cs
Normal file
31
Source/DTFluxRemote/DTFluxRemote.Build.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class DTFluxRemote : ModuleRules
|
||||
{
|
||||
public DTFluxRemote(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"HttpServer",
|
||||
"JsonUtilities",
|
||||
"Json",
|
||||
"DTFluxProjectSettings",
|
||||
"AvalancheMedia"
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
6
Source/DTFluxRemote/Private/DTFluxRemoteMessage.cpp
Normal file
6
Source/DTFluxRemote/Private/DTFluxRemoteMessage.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "DTFluxRemoteMessage.h"
|
||||
|
||||
|
||||
19
Source/DTFluxRemote/Private/DTFluxRemoteModule.cpp
Normal file
19
Source/DTFluxRemote/Private/DTFluxRemoteModule.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "DTFluxRemoteModule.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FDTFluxRemoteModule"
|
||||
|
||||
DEFINE_LOG_CATEGORY(logDTFluxRemote);
|
||||
|
||||
void FDTFluxRemoteModule::StartupModule()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void FDTFluxRemoteModule::ShutdownModule()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FDTFluxRemoteModule, DTFluxRemote)
|
||||
549
Source/DTFluxRemote/Private/DTFluxRemoteSubsystem.cpp
Normal file
549
Source/DTFluxRemote/Private/DTFluxRemoteSubsystem.cpp
Normal file
@ -0,0 +1,549 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "DTFluxRemoteSubsystem.h"
|
||||
#include "DTFluxRemoteSubsystem.h"
|
||||
|
||||
#include "DTFluxGeneralSettings.h"
|
||||
#include "DTFluxRemoteModule.h"
|
||||
#include "DTFluxRemoteModule.h"
|
||||
#include "HttpServerModule.h"
|
||||
#include "IHttpRouter.h"
|
||||
#include "Rundown/AvaRundown.h"
|
||||
#include "Json.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Misc/DateTime.h"
|
||||
|
||||
|
||||
void UDTFluxRemoteSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("DTFlux API Subsystem Initialized"));
|
||||
|
||||
#if WITH_EDITOR
|
||||
// S'abonner aux changements de settings
|
||||
if (UDTFluxGeneralSettings* Settings = GetMutableDefault<UDTFluxGeneralSettings>())
|
||||
{
|
||||
SettingsRundownChangedHandle = Settings->OnRemoteRundownChanged.AddUObject(
|
||||
this, &UDTFluxRemoteSubsystem::OnSettingsRundownChanged
|
||||
);
|
||||
}
|
||||
#endif
|
||||
LoadRundownFromSettings();
|
||||
// Auto-start server (optionnel)
|
||||
StartHTTPServer(63350);
|
||||
}
|
||||
|
||||
void UDTFluxRemoteSubsystem::Deinitialize()
|
||||
{
|
||||
StopHTTPServer();
|
||||
|
||||
|
||||
#if WITH_EDITOR
|
||||
// Se désabonner du delegate
|
||||
if (UDTFluxGeneralSettings* Settings = GetMutableDefault<UDTFluxGeneralSettings>())
|
||||
{
|
||||
if (SettingsRundownChangedHandle.IsValid())
|
||||
{
|
||||
Settings->OnRemoteRundownChanged.Remove(SettingsRundownChangedHandle);
|
||||
SettingsRundownChangedHandle.Reset();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Décharger proprement le rundown
|
||||
UnloadCurrentRundown();
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("DTFlux API Subsystem Deinitialized"));
|
||||
|
||||
Super::Deinitialize();
|
||||
}
|
||||
|
||||
bool UDTFluxRemoteSubsystem::StartHTTPServer(int32 Port)
|
||||
{
|
||||
if (bServerRunning)
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Warning, TEXT("HTTP Server already running on port %d"), ServerPort);
|
||||
return true;
|
||||
}
|
||||
|
||||
ServerPort = Port;
|
||||
|
||||
// Get HTTP Server Module
|
||||
FHttpServerModule& HttpServerModule = FHttpServerModule::Get();
|
||||
|
||||
// Create router
|
||||
HttpRouter = HttpServerModule.GetHttpRouter(ServerPort);
|
||||
|
||||
if (!HttpRouter.IsValid())
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to create HTTP router for port %d"), ServerPort);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup routes
|
||||
SetupRoutes();
|
||||
|
||||
// Start listening
|
||||
HttpServerModule.StartAllListeners();
|
||||
|
||||
bServerRunning = true;
|
||||
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("DTFlux HTTP API Server started on port %d"), ServerPort);
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("Base URL: http://localhost:%d/dtflux/api/v1"), ServerPort);
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("Available routes:"));
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT(" PUT /dtflux/api/v1/title"));
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT(" PUT /dtflux/api/v1/title-bib"));
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT(" PUT /dtflux/api/v1/commands"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UDTFluxRemoteSubsystem::StopHTTPServer()
|
||||
{
|
||||
if (!bServerRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove route handlers
|
||||
if (HttpRouter.IsValid())
|
||||
{
|
||||
HttpRouter->UnbindRoute(TitleRouteHandle);
|
||||
HttpRouter->UnbindRoute(TitleBibRouteHandle);
|
||||
HttpRouter->UnbindRoute(CommandsRouteHandle);
|
||||
}
|
||||
|
||||
// Stop server
|
||||
FHttpServerModule& HttpServerModule = FHttpServerModule::Get();
|
||||
HttpServerModule.StopAllListeners();
|
||||
|
||||
HttpRouter.Reset();
|
||||
bServerRunning = false;
|
||||
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("DTFlux HTTP API Server stopped"));
|
||||
}
|
||||
|
||||
bool UDTFluxRemoteSubsystem::IsHTTPServerRunning() const
|
||||
{
|
||||
return bServerRunning;
|
||||
}
|
||||
|
||||
void UDTFluxRemoteSubsystem::ResetPendingTitleData()
|
||||
{
|
||||
bHasPendingTitleRequest = false;
|
||||
}
|
||||
|
||||
void UDTFluxRemoteSubsystem::ResetPendingBibData()
|
||||
{
|
||||
bHasPendingTitleBibRequest = false;
|
||||
}
|
||||
|
||||
void UDTFluxRemoteSubsystem::SetupRoutes()
|
||||
{
|
||||
if (!HttpRouter.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Route: POST /dtflux/api/v1/title
|
||||
TitleRouteHandle = HttpRouter->BindRoute(
|
||||
FHttpPath(TEXT("/dtflux/api/v1/title")),
|
||||
EHttpServerRequestVerbs::VERB_PUT,
|
||||
FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleTitleRequest)
|
||||
);
|
||||
|
||||
// Route: POST /dtflux/api/v1/title-bib
|
||||
TitleBibRouteHandle = HttpRouter->BindRoute(
|
||||
FHttpPath(TEXT("/dtflux/api/v1/title-bib")),
|
||||
EHttpServerRequestVerbs::VERB_PUT,
|
||||
FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleTitleBibRequest)
|
||||
);
|
||||
|
||||
// Route: POST /dtflux/api/v1/commands
|
||||
CommandsRouteHandle = HttpRouter->BindRoute(
|
||||
FHttpPath(TEXT("/dtflux/api/v1/commands")),
|
||||
EHttpServerRequestVerbs::VERB_PUT,
|
||||
FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleCommandsRequest)
|
||||
);
|
||||
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("HTTP routes configured successfully"));
|
||||
}
|
||||
|
||||
bool UDTFluxRemoteSubsystem::HandleTitleRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete)
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("Received Title request"));
|
||||
|
||||
// Parse JSON
|
||||
TSharedPtr<FJsonObject> JsonObject = ParseJsonFromRequest(Request);
|
||||
if (!JsonObject.IsValid())
|
||||
{
|
||||
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid JSON")), TEXT("application/json")));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse title data
|
||||
FDTFluxRemoteTitleData TitleData;
|
||||
if (!ParseTitleData(JsonObject, TitleData))
|
||||
{
|
||||
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid title data format")), TEXT("application/json")));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Broadcast event (execute on Game Thread)
|
||||
AsyncTask(ENamedThreads::GameThread, [this, TitleData]()
|
||||
{
|
||||
OnTitleReceived.Broadcast(TitleData);
|
||||
if (RemotedRundown && RemotedRundown->IsValidLowLevel())
|
||||
{
|
||||
PendingTitleData = TitleData;
|
||||
bHasPendingTitleRequest = true;
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("Playing page %i"), TitleData.RundownPageId);
|
||||
RemotedRundown->PlayPage(TitleData.RundownPageId, EAvaRundownPagePlayType::PlayFromStart);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Warning, TEXT("No rundown loaded"));
|
||||
}
|
||||
});
|
||||
|
||||
OnComplete(FHttpServerResponse::Create(CreateSuccessResponse(TEXT("Title data received")), TEXT("application/json")));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDTFluxRemoteSubsystem::HandleTitleBibRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete)
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("Received Title-Bib request"));
|
||||
|
||||
TSharedPtr<FJsonObject> JsonObject = ParseJsonFromRequest(Request);
|
||||
if (!JsonObject.IsValid())
|
||||
{
|
||||
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid JSON")), TEXT("application/json")));
|
||||
return true;
|
||||
}
|
||||
|
||||
FDTFluxRemoteBibData BibData;
|
||||
if (!ParseTitleBibData(JsonObject, BibData))
|
||||
{
|
||||
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid title-bib data format")), TEXT("application/json")));
|
||||
return true;
|
||||
}
|
||||
|
||||
AsyncTask(ENamedThreads::GameThread, [this, BibData]()
|
||||
{
|
||||
OnTitleBibReceived.Broadcast(BibData);
|
||||
});
|
||||
|
||||
OnComplete(FHttpServerResponse::Create(CreateSuccessResponse(TEXT("Title-bib data received")), TEXT("application/json")));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDTFluxRemoteSubsystem::HandleCommandsRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete)
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("Received Commands request"));
|
||||
|
||||
TSharedPtr<FJsonObject> JsonObject = ParseJsonFromRequest(Request);
|
||||
if (!JsonObject.IsValid())
|
||||
{
|
||||
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid JSON")), TEXT("application/json")));
|
||||
return true;
|
||||
}
|
||||
|
||||
FDTFluxRemoteCommandData CommandData;
|
||||
if (!ParseCommandData(JsonObject, CommandData))
|
||||
{
|
||||
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid command data format")), TEXT("application/json")));
|
||||
return true;
|
||||
}
|
||||
|
||||
AsyncTask(ENamedThreads::GameThread, [this, CommandData]()
|
||||
{
|
||||
OnCommandReceived.Broadcast(CommandData);
|
||||
if (RemotedRundown && RemotedRundown->IsValidLowLevel())
|
||||
{
|
||||
RemotedRundown->StopPage(CommandData.RundownPageId, EAvaRundownPageStopOptions::None, false);
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("Stoping page %i"), CommandData.RundownPageId);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Warning, TEXT("No rundown loaded"));
|
||||
}
|
||||
});
|
||||
|
||||
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("OK")), TEXT("application/json")));
|
||||
return true;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> UDTFluxRemoteSubsystem::ParseJsonFromRequest(const FHttpServerRequest& Request)
|
||||
{
|
||||
// Get request body
|
||||
TArray<uint8> Body = Request.Body;
|
||||
FString JsonString;
|
||||
if (Body.Num() > 0)
|
||||
{
|
||||
// Ajouter un null terminator si nécessaire
|
||||
if (Body.Last() != 0)
|
||||
{
|
||||
Body.Add(0);
|
||||
}
|
||||
|
||||
JsonString = FString(UTF8_TO_TCHAR(reinterpret_cast<const char*>(Body.GetData())));
|
||||
}
|
||||
|
||||
UE_LOG(logDTFluxRemote, Verbose, TEXT("Received JSON: %s"), *JsonString);
|
||||
|
||||
// Parse JSON
|
||||
TSharedPtr<FJsonObject> JsonObject;
|
||||
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
|
||||
|
||||
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to parse JSON: %s"), *JsonString);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return JsonObject;
|
||||
}
|
||||
|
||||
FString UDTFluxRemoteSubsystem::CreateSuccessResponse(const FString& Message)
|
||||
{
|
||||
TSharedPtr<FJsonObject> ResponseObject = MakeShareable(new FJsonObject);
|
||||
ResponseObject->SetBoolField(TEXT("success"), true);
|
||||
ResponseObject->SetStringField(TEXT("message"), Message);
|
||||
ResponseObject->SetStringField(TEXT("timestamp"), FDateTime::Now().ToIso8601());
|
||||
|
||||
FString OutputString;
|
||||
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
|
||||
FJsonSerializer::Serialize(ResponseObject.ToSharedRef(), Writer);
|
||||
|
||||
return OutputString;
|
||||
}
|
||||
|
||||
FString UDTFluxRemoteSubsystem::CreateErrorResponse(const FString& Error, int32 Code)
|
||||
{
|
||||
TSharedPtr<FJsonObject> ResponseObject = MakeShareable(new FJsonObject);
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), Error);
|
||||
ResponseObject->SetNumberField(TEXT("code"), Code);
|
||||
ResponseObject->SetStringField(TEXT("timestamp"), FDateTime::Now().ToIso8601());
|
||||
|
||||
FString OutputString;
|
||||
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
|
||||
FJsonSerializer::Serialize(ResponseObject.ToSharedRef(), Writer);
|
||||
|
||||
return OutputString;
|
||||
}
|
||||
|
||||
bool UDTFluxRemoteSubsystem::ParseTitleData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteTitleData& OutData)
|
||||
{
|
||||
if (!JsonObject.IsValid())
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Error, TEXT("Invalid JSON object for %s"), TEXT("FDTFluxRemoteTitleData"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FJsonObjectConverter::JsonObjectToUStruct<FDTFluxRemoteTitleData>(JsonObject.ToSharedRef(), &OutData))
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("Successfully parsed %s"), TEXT("FDTFluxRemoteTitleData"));
|
||||
return true;
|
||||
}
|
||||
|
||||
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to convert JSON to %s struct"),TEXT("FDTFluxRemoteTitleData"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UDTFluxRemoteSubsystem::ParseTitleBibData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteBibData& OutData)
|
||||
{
|
||||
if (!JsonObject.IsValid())
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Error, TEXT("Invalid JSON object for %s"), TEXT("FDTFluxRemoteBibData"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FJsonObjectConverter::JsonObjectToUStruct<FDTFluxRemoteBibData>(JsonObject.ToSharedRef(), &OutData))
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("Successfully parsed %s"), TEXT("FDTFluxRemoteBibData"));
|
||||
return true;
|
||||
}
|
||||
|
||||
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to convert JSON to %s struct"),TEXT("FDTFluxRemoteBibData"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UDTFluxRemoteSubsystem::ParseCommandData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteCommandData& OutData)
|
||||
{
|
||||
if (!JsonObject.IsValid())
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Error, TEXT("Invalid JSON object for %s"), TEXT("FDTFluxRemoteCommandData"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FJsonObjectConverter::JsonObjectToUStruct<FDTFluxRemoteCommandData>(JsonObject.ToSharedRef(), &OutData))
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("Successfully parsed %s"), TEXT("FDTFluxRemoteCommandData"));
|
||||
return true;
|
||||
}
|
||||
|
||||
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to convert JSON to %s struct"),TEXT("FDTFluxRemoteCommandData"));
|
||||
return false;
|
||||
}
|
||||
|
||||
void UDTFluxRemoteSubsystem::UnloadCurrentRundown()
|
||||
{
|
||||
if (RemotedRundown)
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("Unloading current rundown"));
|
||||
// Ici vous pouvez ajouter une logique de nettoyage si nécessaire
|
||||
// Par exemple : RemotedRundown->StopAllPages();
|
||||
RemotedRundown = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void UDTFluxRemoteSubsystem::LoadRundownFromSettings()
|
||||
{
|
||||
const UDTFluxGeneralSettings* Settings = GetDefault<UDTFluxGeneralSettings>();
|
||||
if (!Settings)
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Warning, TEXT("Cannot access DTFlux settings"));
|
||||
return;
|
||||
}
|
||||
|
||||
TSoftObjectPtr<UAvaRundown> RundownAsset = Settings->RemoteTargetRundown;
|
||||
|
||||
if (RundownAsset.IsNull())
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("No rundown specified in settings"));
|
||||
UnloadCurrentRundown();
|
||||
return;
|
||||
}
|
||||
|
||||
if (RemotedRundown && RemotedRundown == RundownAsset.LoadSynchronous())
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("Rundown already loaded: %s"), *RundownAsset.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
// Décharger l'ancien rundown d'abord
|
||||
UnloadCurrentRundown();
|
||||
RundownAsset = RundownAsset.LoadSynchronous();
|
||||
// Charger le nouveau rundown
|
||||
if ( RundownAsset.IsValid())
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("Successfully loaded rundown from settings: %s"), *RundownAsset.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to load rundown from settings: %s"), *RundownAsset.ToString());
|
||||
}
|
||||
LoadRundown(RundownAsset);
|
||||
}
|
||||
|
||||
bool UDTFluxRemoteSubsystem::LoadRundown(const TSoftObjectPtr<UAvaRundown>& RundownAsset)
|
||||
{
|
||||
if (RundownAsset.IsNull())
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Warning, TEXT("Cannot load rundown: asset is null"));
|
||||
UnloadCurrentRundown();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Charger le rundown de manière synchrone
|
||||
UAvaRundown* LoadedRundown = RundownAsset.LoadSynchronous();
|
||||
|
||||
// Vérifier si le rundown est déjà chargé
|
||||
if (RemotedRundown && RemotedRundown == LoadedRundown)
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("Rundown already loaded: %s"), *RundownAsset.ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Décharger l'ancien rundown d'abord
|
||||
UnloadCurrentRundown();
|
||||
|
||||
// Assigner le nouveau rundown
|
||||
RemotedRundown = LoadedRundown;
|
||||
|
||||
// Vérifier que le chargement a réussi
|
||||
if (RemotedRundown && RemotedRundown->IsValidLowLevel())
|
||||
{
|
||||
RemotedRundown->InitializePlaybackContext();
|
||||
UE_LOG(logDTFluxRemote, Log, TEXT("Successfully loaded rundown: %s"), *RundownAsset.ToString());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to load rundown: %s"), *RundownAsset.ToString());
|
||||
RemotedRundown = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UDTFluxRemoteSubsystem::OnSettingsRundownChanged(const TSoftObjectPtr<UAvaRundown>& NewRundown)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Manual processing functions for testing
|
||||
bool UDTFluxRemoteSubsystem::ProcessTitleData(const FString& JsonString)
|
||||
{
|
||||
TSharedPtr<FJsonObject> JsonObject;
|
||||
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
|
||||
|
||||
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
FDTFluxRemoteTitleData TitleData;
|
||||
if (ParseTitleData(JsonObject, TitleData))
|
||||
{
|
||||
OnTitleReceived.Broadcast(TitleData);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UDTFluxRemoteSubsystem::ProcessTitleBibData(const FString& JsonString)
|
||||
{
|
||||
TSharedPtr<FJsonObject> JsonObject;
|
||||
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
|
||||
|
||||
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FDTFluxRemoteBibData TitleBibData;
|
||||
if (ParseTitleBibData(JsonObject, TitleBibData))
|
||||
{
|
||||
OnTitleBibReceived.Broadcast(TitleBibData);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UDTFluxRemoteSubsystem::ProcessCommandData(const FString& JsonString)
|
||||
{
|
||||
TSharedPtr<FJsonObject> JsonObject;
|
||||
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
|
||||
|
||||
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FDTFluxRemoteCommandData CommandData;
|
||||
if (ParseCommandData(JsonObject, CommandData))
|
||||
{
|
||||
OnCommandReceived.Broadcast(CommandData);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
220
Source/DTFluxRemote/Private/DTFluxRemotedLevelController.cpp
Normal file
220
Source/DTFluxRemote/Private/DTFluxRemotedLevelController.cpp
Normal file
@ -0,0 +1,220 @@
|
||||
// DTFluxRemoteActor.cpp
|
||||
#include "DTFluxRemotedLevelController.h"
|
||||
#include "DTFluxRemoteSubsystem.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine/World.h"
|
||||
|
||||
ADTFluxRemotedLevelController::ADTFluxRemotedLevelController()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = false;
|
||||
RemoteSubsystem = nullptr;
|
||||
bEventsBound = false;
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Constructor called"));
|
||||
}
|
||||
|
||||
void ADTFluxRemotedLevelController::PostInitializeComponents()
|
||||
{
|
||||
Super::PostInitializeComponents();
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: PostInitializeComponents called"));
|
||||
|
||||
// Essayer de bind dès que possible
|
||||
InitializeSubsystemBinding();
|
||||
}
|
||||
|
||||
void ADTFluxRemotedLevelController::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: BeginPlay called"));
|
||||
|
||||
// S'assurer que le binding est fait (au cas où PostInitializeComponents aurait échoué)
|
||||
if (!bEventsBound)
|
||||
{
|
||||
InitializeSubsystemBinding();
|
||||
}
|
||||
}
|
||||
|
||||
void ADTFluxRemotedLevelController::InitializeSubsystemBinding()
|
||||
{
|
||||
// Éviter le double binding
|
||||
if (bEventsBound)
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Events already bound, skipping"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Récupérer le subsystem
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
RemoteSubsystem = GEngine->GetEngineSubsystem<UDTFluxRemoteSubsystem>();
|
||||
|
||||
if (RemoteSubsystem)
|
||||
{
|
||||
// Bind les events du subsystem
|
||||
RemoteSubsystem->OnTitleReceived.AddDynamic(
|
||||
this, &ADTFluxRemotedLevelController::OnTitleDataReceived
|
||||
);
|
||||
RemoteSubsystem->OnTitleBibReceived.AddDynamic(
|
||||
this, &ADTFluxRemotedLevelController::OnTitleBibDataReceived
|
||||
);
|
||||
|
||||
RemoteSubsystem->OnCommandReceived.AddDynamic(
|
||||
this, &ADTFluxRemotedLevelController::OnCommandDataReceived
|
||||
);
|
||||
|
||||
bEventsBound = true;
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Successfully bound to subsystem events"));
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("DTFluxRemoteActor: DTFluxRemoteSubsystem not available yet"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("DTFluxRemoteActor: World not available yet"));
|
||||
}
|
||||
}
|
||||
|
||||
void ADTFluxRemotedLevelController::EnsureSubsystemBinding()
|
||||
{
|
||||
if (!bEventsBound)
|
||||
{
|
||||
InitializeSubsystemBinding();
|
||||
}
|
||||
}
|
||||
|
||||
void ADTFluxRemotedLevelController::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
// Unbind les events pour éviter les fuites mémoire
|
||||
if (RemoteSubsystem && bEventsBound)
|
||||
{
|
||||
if (TitleReceivedHandle.IsValid())
|
||||
{
|
||||
RemoteSubsystem->OnTitleReceived.RemoveDynamic(this, &ADTFluxRemotedLevelController::OnTitleDataReceived);
|
||||
TitleReceivedHandle.Reset();
|
||||
}
|
||||
|
||||
if (TitleBibReceivedHandle.IsValid())
|
||||
{
|
||||
RemoteSubsystem->OnTitleBibReceived.RemoveDynamic(this, &ADTFluxRemotedLevelController::OnTitleBibDataReceived);
|
||||
TitleBibReceivedHandle.Reset();
|
||||
}
|
||||
|
||||
if (CommandReceivedHandle.IsValid())
|
||||
{
|
||||
RemoteSubsystem->OnCommandReceived.RemoveDynamic(this, &ADTFluxRemotedLevelController::OnCommandDataReceived);
|
||||
CommandReceivedHandle.Reset();
|
||||
}
|
||||
|
||||
bEventsBound = false;
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Unbound from subsystem events"));
|
||||
}
|
||||
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
|
||||
void ADTFluxRemotedLevelController::OnTitleDataReceived(const FDTFluxRemoteTitleData& TitleData)
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Received Title Data - %s %s (RundownPageId: %d)"),
|
||||
*TitleData.FirstName, *TitleData.LastName, TitleData.RundownPageId);
|
||||
|
||||
// Broadcast l'event Blueprint
|
||||
OnTitleReceived.Broadcast(TitleData);
|
||||
|
||||
// Appeler l'event Blueprint implémentable
|
||||
BP_OnTitleDataReceived(TitleData);
|
||||
|
||||
// Appeler la fonction virtuelle C++ (peut être overridée dans les classes dérivées)
|
||||
HandleTitleData(TitleData);
|
||||
}
|
||||
|
||||
void ADTFluxRemotedLevelController::OnTitleBibDataReceived(const FDTFluxRemoteBibData& BibData)
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Received Title Bib Data - Bib: %d"), BibData.Bib);
|
||||
|
||||
// Broadcast l'event Blueprint
|
||||
OnTitleBibReceived.Broadcast(BibData);
|
||||
|
||||
// Appeler l'event Blueprint implémentable
|
||||
BP_OnTitleBibDataReceived(BibData);
|
||||
|
||||
// Appeler la fonction virtuelle C++ (peut être overridée dans les classes dérivées)
|
||||
HandleTitleBibData(BibData);
|
||||
}
|
||||
|
||||
void ADTFluxRemotedLevelController::OnCommandDataReceived(const FDTFluxRemoteCommandData& CommandData)
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Received Command Data - Type: %s, : RundownPageId %i"),
|
||||
*CommandData.Type, CommandData.RundownPageId);
|
||||
|
||||
// Broadcast l'event Blueprint
|
||||
OnCommandReceived.Broadcast(CommandData);
|
||||
|
||||
// Appeler l'event Blueprint implémentable
|
||||
BP_OnCommandDataReceived(CommandData);
|
||||
|
||||
// Appeler la fonction virtuelle C++ (peut être overridée dans les classes dérivées)
|
||||
HandleCommandData(CommandData);
|
||||
}
|
||||
|
||||
bool ADTFluxRemotedLevelController::IsSubsystemAvailable() const
|
||||
{
|
||||
return RemoteSubsystem && RemoteSubsystem->IsValidLowLevel();
|
||||
}
|
||||
|
||||
bool ADTFluxRemotedLevelController::IsHTTPServerRunning() const
|
||||
{
|
||||
if (RemoteSubsystem)
|
||||
{
|
||||
return RemoteSubsystem->IsHTTPServerRunning();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ADTFluxRemotedLevelController::StartHTTPServer(int32 Port)
|
||||
{
|
||||
if (RemoteSubsystem)
|
||||
{
|
||||
RemoteSubsystem->StartHTTPServer(Port);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("DTFluxRemoteActor: Cannot start HTTP server - subsystem not available"));
|
||||
}
|
||||
}
|
||||
|
||||
void ADTFluxRemotedLevelController::StopHTTPServer()
|
||||
{
|
||||
if (RemoteSubsystem)
|
||||
{
|
||||
RemoteSubsystem->StopHTTPServer();
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("DTFluxRemoteActor: Cannot stop HTTP server - subsystem not available"));
|
||||
}
|
||||
}
|
||||
|
||||
// Implémentations par défaut des fonctions virtuelles C++
|
||||
void ADTFluxRemotedLevelController::HandleTitleData_Implementation(const FDTFluxRemoteTitleData& TitleData)
|
||||
{
|
||||
// Implémentation par défaut - peut être overridée dans les classes dérivées
|
||||
UE_LOG(LogTemp, Verbose, TEXT("DTFluxRemoteActor: Handling Title Data (default implementation)"));
|
||||
}
|
||||
|
||||
void ADTFluxRemotedLevelController::HandleTitleBibData_Implementation(const FDTFluxRemoteBibData& BibData)
|
||||
{
|
||||
// Implémentation par défaut - peut être overridée dans les classes dérivées
|
||||
UE_LOG(LogTemp, Verbose, TEXT("DTFluxRemoteActor: Handling Title Bib Data (default implementation)"));
|
||||
}
|
||||
|
||||
void ADTFluxRemotedLevelController::HandleCommandData_Implementation(const FDTFluxRemoteCommandData& CommandData)
|
||||
{
|
||||
// Implémentation par défaut - peut être overridée dans les classes dérivées
|
||||
UE_LOG(LogTemp, Verbose, TEXT("DTFluxRemoteActor: Handling Command Data (default implementation)"));
|
||||
}
|
||||
76
Source/DTFluxRemote/Public/DTFluxRemoteMessage.h
Normal file
76
Source/DTFluxRemote/Public/DTFluxRemoteMessage.h
Normal file
@ -0,0 +1,76 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "DTFluxRemoteMessage.generated.h"
|
||||
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FDTFluxRemoteBasicData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
|
||||
FDateTime UpdateAt = FDateTime::Now();
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
|
||||
int RundownPageId = -1;
|
||||
FDTFluxRemoteBasicData() = default;
|
||||
FDTFluxRemoteBasicData(const FDateTime& InUpdateAt): UpdateAt(InUpdateAt){};
|
||||
};
|
||||
|
||||
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FDTFluxRemoteTitleData : public FDTFluxRemoteBasicData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
|
||||
FString FirstName = "";
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
|
||||
FString LastName = "";
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
|
||||
FString Function1 = "";
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
|
||||
FString Function2 = "";
|
||||
|
||||
|
||||
FDTFluxRemoteTitleData() = default;
|
||||
FDTFluxRemoteTitleData(const FString InFirstName, const FString InLastName, const FString InFunction1, const FString InFunction2):
|
||||
FirstName(InFirstName), LastName(InLastName), Function1(InFunction1), Function2(InFunction2){};
|
||||
};
|
||||
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FDTFluxRemoteBibData : public FDTFluxRemoteBasicData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
|
||||
int Bib = -1;
|
||||
|
||||
FDTFluxRemoteBibData() = default;
|
||||
FDTFluxRemoteBibData(int InBib): Bib(InBib){};
|
||||
};
|
||||
USTRUCT(BlueprintType)
|
||||
struct FDTFluxRemoteCommandData : public FDTFluxRemoteBasicData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
FDTFluxRemoteCommandData() = default;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
|
||||
FString Type = "Stop";
|
||||
|
||||
|
||||
FDTFluxRemoteCommandData(FString InType):
|
||||
Type(InType){};
|
||||
};
|
||||
|
||||
13
Source/DTFluxRemote/Public/DTFluxRemoteModule.h
Normal file
13
Source/DTFluxRemote/Public/DTFluxRemoteModule.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
DTFLUXREMOTE_API DECLARE_LOG_CATEGORY_EXTERN(logDTFluxRemote, Log, All);
|
||||
|
||||
class DTFLUXREMOTE_API FDTFluxRemoteModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
||||
117
Source/DTFluxRemote/Public/DTFluxRemoteSubsystem.h
Normal file
117
Source/DTFluxRemote/Public/DTFluxRemoteSubsystem.h
Normal file
@ -0,0 +1,117 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/EngineSubsystem.h"
|
||||
#include "DTFluxRemoteMessage.h"
|
||||
#include "HttpRouteHandle.h"
|
||||
#include "IHttpRouter.h"
|
||||
#include "DTFluxRemoteSubsystem.generated.h"
|
||||
|
||||
class UAvaRundown;
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTitleReceived, const FDTFluxRemoteTitleData&, TitleData);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTitleBibReceived, const FDTFluxRemoteBibData&, TitleBibData);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCommandReceived, const FDTFluxRemoteCommandData&, CommandData);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UCLASS(BlueprintType, Category="DTFlux|Remote")
|
||||
class DTFLUXREMOTE_API UDTFluxRemoteSubsystem : public UEngineSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
virtual void Deinitialize() override;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "DTFlux API")
|
||||
FOnTitleReceived OnTitleReceived;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "DTFlux API")
|
||||
FOnTitleBibReceived OnTitleBibReceived;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "DTFlux API")
|
||||
FOnCommandReceived OnCommandReceived;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
|
||||
bool StartHTTPServer(int32 Port = 63350);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
|
||||
void StopHTTPServer();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux API", BlueprintPure)
|
||||
bool IsHTTPServerRunning() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux API", BlueprintPure)
|
||||
int32 GetServerPort() const { return ServerPort; }
|
||||
|
||||
// Manual data processing (for testing)
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
|
||||
bool ProcessTitleData(const FString& JsonString);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
|
||||
bool ProcessTitleBibData(const FString& JsonString);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
|
||||
bool ProcessCommandData(const FString& JsonString);
|
||||
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "DTFlux API")
|
||||
bool bHasPendingTitleRequest = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "DTFlux API")
|
||||
bool bHasPendingTitleBibRequest = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "DTFlux API")
|
||||
FDTFluxRemoteTitleData PendingTitleData;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "DTFlux API")
|
||||
FDTFluxRemoteBibData PendingTitleBibData;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
|
||||
void ResetPendingTitleData();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
|
||||
void ResetPendingBibData();
|
||||
|
||||
private:
|
||||
void SetupRoutes();
|
||||
|
||||
bool HandleTitleRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete);
|
||||
bool HandleTitleBibRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete);
|
||||
bool HandleCommandsRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete);
|
||||
|
||||
TSharedPtr<FJsonObject> ParseJsonFromRequest(const FHttpServerRequest& Request);
|
||||
FString CreateSuccessResponse(const FString& Message = TEXT("Success"));
|
||||
FString CreateErrorResponse(const FString& Error, int32 Code = 400);
|
||||
|
||||
bool ParseTitleData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteTitleData& OutData);
|
||||
bool ParseTitleBibData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteBibData& OutData);
|
||||
bool ParseCommandData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteCommandData& OutData);
|
||||
|
||||
private:
|
||||
TSharedPtr<IHttpRouter> HttpRouter;
|
||||
TSoftObjectPtr<UAvaRundown> RemotedRundown;
|
||||
int32 ServerPort = 63350;
|
||||
bool bServerRunning = false;
|
||||
|
||||
FHttpRouteHandle TitleRouteHandle;
|
||||
FHttpRouteHandle TitleBibRouteHandle;
|
||||
FHttpRouteHandle CommandsRouteHandle;
|
||||
|
||||
void UnloadCurrentRundown();
|
||||
void LoadRundownFromSettings();
|
||||
bool LoadRundown(const TSoftObjectPtr<UAvaRundown>& RundownAsset);
|
||||
|
||||
#if WITH_EDITOR
|
||||
FDelegateHandle SettingsRundownChangedHandle;
|
||||
#endif
|
||||
|
||||
|
||||
#if WITH_EDITOR
|
||||
// Callback pour les changements de settings
|
||||
UFUNCTION()
|
||||
void OnSettingsRundownChanged(const TSoftObjectPtr<UAvaRundown>& NewRundown);
|
||||
#endif
|
||||
};
|
||||
94
Source/DTFluxRemote/Public/DTFluxRemotedLevelController.h
Normal file
94
Source/DTFluxRemote/Public/DTFluxRemotedLevelController.h
Normal file
@ -0,0 +1,94 @@
|
||||
// DTFluxRemotedLevelController.h
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "DTFluxRemoteSubsystem.h"
|
||||
#include "DTFluxRemotedLevelController.generated.h"
|
||||
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class DTFLUXREMOTE_API ADTFluxRemotedLevelController : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
ADTFluxRemotedLevelController();
|
||||
|
||||
protected:
|
||||
virtual void PostInitializeComponents() override;
|
||||
virtual void BeginPlay() override;
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
// Subsystem et binding
|
||||
UPROPERTY(BlueprintReadOnly, Category = "DTFlux")
|
||||
UDTFluxRemoteSubsystem* RemoteSubsystem;
|
||||
|
||||
FDelegateHandle TitleReceivedHandle;
|
||||
FDelegateHandle TitleBibReceivedHandle;
|
||||
FDelegateHandle CommandReceivedHandle;
|
||||
bool bEventsBound;
|
||||
|
||||
// Fonctions de binding
|
||||
void InitializeSubsystemBinding();
|
||||
|
||||
// ✅ CORRECTION : Callbacks avec UFUNCTION()
|
||||
UFUNCTION()
|
||||
void OnTitleDataReceived(const FDTFluxRemoteTitleData& TitleData);
|
||||
|
||||
UFUNCTION()
|
||||
void OnTitleBibDataReceived(const FDTFluxRemoteBibData& BibData);
|
||||
|
||||
UFUNCTION()
|
||||
void OnCommandDataReceived(const FDTFluxRemoteCommandData& CommandData);
|
||||
|
||||
public:
|
||||
// Events Blueprint-friendly
|
||||
UPROPERTY(BlueprintAssignable, Category = "DTFlux Events")
|
||||
FOnTitleReceived OnTitleReceived;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "DTFlux Events")
|
||||
FOnTitleBibReceived OnTitleBibReceived;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "DTFlux Events")
|
||||
FOnCommandReceived OnCommandReceived;
|
||||
|
||||
// Fonctions utilitaires
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||
bool IsSubsystemAvailable() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||
bool IsHTTPServerRunning() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||
void StartHTTPServer(int32 Port = 63350);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||
void StopHTTPServer();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||
void EnsureSubsystemBinding();
|
||||
|
||||
protected:
|
||||
// Events Blueprint implémentables
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "DTFlux Events")
|
||||
void BP_OnTitleDataReceived(const FDTFluxRemoteTitleData& TitleData);
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "DTFlux Events")
|
||||
void BP_OnTitleBibDataReceived(const FDTFluxRemoteBibData& BibData);
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "DTFlux Events")
|
||||
void BP_OnCommandDataReceived(const FDTFluxRemoteCommandData& CommandData);
|
||||
|
||||
// Fonctions virtuelles C++
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "DTFlux Events")
|
||||
void HandleTitleData(const FDTFluxRemoteTitleData& TitleData);
|
||||
virtual void HandleTitleData_Implementation(const FDTFluxRemoteTitleData& TitleData);
|
||||
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "DTFlux Events")
|
||||
void HandleTitleBibData(const FDTFluxRemoteBibData& BibData);
|
||||
virtual void HandleTitleBibData_Implementation(const FDTFluxRemoteBibData& BibData);
|
||||
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "DTFlux Events")
|
||||
void HandleCommandData(const FDTFluxRemoteCommandData& CommandData);
|
||||
virtual void HandleCommandData_Implementation(const FDTFluxRemoteCommandData& CommandData);
|
||||
};
|
||||
@ -22,6 +22,7 @@ public class DTFluxUtilities : ModuleRules
|
||||
"SlateCore",
|
||||
"DTFluxCore",
|
||||
"DTFluxCoreSubsystem",
|
||||
"AvalancheMedia",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "FTDFluxUtils.h"
|
||||
#include "DTFluxUtils.h"
|
||||
|
||||
#include "DTFluxCoreSubsystem.h"
|
||||
#include "DTFluxUtilitiesModule.h"
|
||||
@ -55,3 +55,13 @@ void UFTDFluxUtils::GetFullName(const int Bib, FText& OutFullName)
|
||||
}
|
||||
UE_LOG(logDTFluxUtilities, Error, TEXT("DTFluxCoreSubsystem not available"));
|
||||
}
|
||||
|
||||
TArray<FDTFluxSplitSensorInfo> UFTDFluxUtils::SortSplitRankingsByRank(const TArray<FDTFluxSplitSensorInfo>& Rankings)
|
||||
{
|
||||
TArray<FDTFluxSplitSensorInfo> CopyRankings = Rankings;
|
||||
CopyRankings.Sort([](const FDTFluxSplitSensorInfo& A, const FDTFluxSplitSensorInfo& B)
|
||||
{
|
||||
return A.Rank < B.Rank;
|
||||
});
|
||||
return CopyRankings;
|
||||
}
|
||||
114
Source/DTFluxUtilities/Private/RundownController.cpp
Normal file
114
Source/DTFluxUtilities/Private/RundownController.cpp
Normal file
@ -0,0 +1,114 @@
|
||||
// ===============================================
|
||||
// 3. SOURCE FILE (.CPP) - TOUS LES INCLUDES
|
||||
// ===============================================
|
||||
|
||||
// YourRundownController.cpp
|
||||
#include "RundownController.h"
|
||||
|
||||
#include "DTFluxUtilitiesModule.h"
|
||||
#include "Rundown/AvaRundown.h"
|
||||
#include "Rundown/AvaRundownPage.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine/World.h"
|
||||
#include "UObject/UObjectGlobals.h"
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
#include "TimerManager.h"
|
||||
#include "Logging/LogMacros.h"
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
#include "Materials/MaterialInterface.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogYourRundownController, Log, All);
|
||||
|
||||
// ===============================================
|
||||
// 4. IMPLÉMENTATION SIMPLE
|
||||
// ===============================================
|
||||
|
||||
ARundownController::ARundownController()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = false;
|
||||
}
|
||||
|
||||
bool ARundownController::LoadRundown(const TSoftObjectPtr<UAvaRundown> RundownAsset)
|
||||
{
|
||||
if (RundownAsset.IsNull())
|
||||
{
|
||||
UE_LOG(logDTFluxUtilities, Error, TEXT("RundownAsset Null"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Charger l'asset rundown
|
||||
CurrentRundown = RundownAsset.LoadSynchronous();
|
||||
|
||||
if (!CurrentRundown)
|
||||
{
|
||||
UE_LOG(LogYourRundownController, Error, TEXT("Failed to load rundown: %s"), *RundownAsset.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialiser le contexte de playback
|
||||
CurrentRundown->InitializePlaybackContext();
|
||||
|
||||
UE_LOG(LogYourRundownController, Log, TEXT("Successfully loaded rundown: %s"), *RundownAsset.ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ARundownController::PlayPage(int32 PageId)
|
||||
{
|
||||
if (!CurrentRundown)
|
||||
{
|
||||
UE_LOG(LogYourRundownController, Error, TEXT("No rundown loaded. Call LoadRundown first."));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifier que la page existe
|
||||
const FAvaRundownPage& Page = CurrentRundown->GetPage(PageId);
|
||||
if (!Page.IsValidPage())
|
||||
{
|
||||
UE_LOG(LogYourRundownController, Error, TEXT("Invalid page ID: %d"), PageId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Jouer la page
|
||||
bool bSuccess = CurrentRundown->PlayPage(PageId, EAvaRundownPagePlayType::PlayFromStart);
|
||||
|
||||
if (bSuccess)
|
||||
{
|
||||
CurrentPageId = PageId;
|
||||
UE_LOG(LogYourRundownController, Log, TEXT("Playing page %d: %s"), PageId, *Page.GetPageName());
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogYourRundownController, Warning, TEXT("Failed to play page %d"), PageId);
|
||||
}
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
bool ARundownController::StopPage(int32 PageId)
|
||||
{
|
||||
if (!CurrentRundown)
|
||||
{
|
||||
UE_LOG(LogYourRundownController, Error, TEXT("No rundown loaded"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bSuccess = CurrentRundown->StopPage(PageId, EAvaRundownPageStopOptions::Default, false);
|
||||
|
||||
if (bSuccess)
|
||||
{
|
||||
UE_LOG(LogYourRundownController, Log, TEXT("Stopped page %d"), PageId);
|
||||
}
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
FString ARundownController::ListePages()
|
||||
{
|
||||
if (!CurrentRundown)
|
||||
{
|
||||
UE_LOG(logDTFluxUtilities, Error, TEXT("No rundown loaded"));
|
||||
return FString();
|
||||
}
|
||||
return FString();
|
||||
}
|
||||
@ -5,8 +5,8 @@
|
||||
#include "CoreMinimal.h"
|
||||
#include "DTFluxCore/Public/Types/Struct/DTFluxTeamListStruct.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "Types/Struct/DTFluxRankingStructs.h"
|
||||
#include "FTDFluxUtils.generated.h"
|
||||
#include "Types/Struct/DTFluxSplitSensor.h"
|
||||
#include "DTFluxUtils.generated.h"
|
||||
|
||||
/**
|
||||
*
|
||||
@ -70,4 +70,8 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils")
|
||||
static void GetFullName(const int Bib, FText& OutFullName);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils")
|
||||
static TArray<FDTFluxSplitSensorInfo> SortSplitRankingsByRank(const TArray<FDTFluxSplitSensorInfo>& Rankings);
|
||||
};
|
||||
|
||||
|
||||
41
Source/DTFluxUtilities/Public/RundownController.h
Normal file
41
Source/DTFluxUtilities/Public/RundownController.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Engine/World.h"
|
||||
#include "TimerManager.h"
|
||||
#include "Rundown/AvaRundown.h"
|
||||
#include "UObject/SoftObjectPath.h"
|
||||
#include "UObject/ObjectMacros.h"
|
||||
#include "RundownController.generated.h"
|
||||
|
||||
|
||||
|
||||
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class DTFLUXUTILITIES_API ARundownController : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
ARundownController();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||
bool LoadRundown(const TSoftObjectPtr<UAvaRundown> RundownAsset);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||
bool PlayPage(int32 PageId);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||
bool StopPage(int32 PageId);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||
FString ListePages();
|
||||
|
||||
protected:
|
||||
UPROPERTY(BlueprintReadOnly, Category = "DTFlux")
|
||||
TObjectPtr<UAvaRundown> CurrentRundown;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "DTFlux")
|
||||
int32 CurrentPageId = 1;
|
||||
};
|
||||
Reference in New Issue
Block a user