Compare commits

...

1 Commits

Author SHA1 Message Date
da89e35eb2 Added Module Remote To add HTTP basic RemoteControl 2025-07-16 02:41:08 +02:00
13 changed files with 769 additions and 25 deletions

View File

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

View File

@ -432,6 +432,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 +549,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 +608,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 +621,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"));

View File

@ -121,7 +121,6 @@ public:
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
FGuid InitSplitRankingsDisplay(const int ContestId, const int StageId, const int SplitId);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetStageRankingForBib(const int ContestId, const int StageId, const int Bib,
@ -129,8 +128,8 @@ public:
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")

View File

@ -0,0 +1,29 @@
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",
}
);
}
}

View File

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

View File

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

View File

@ -0,0 +1,376 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "DTFluxRemoteSubsystem.h"
#include "DTFluxRemoteSubsystem.h"
#include "DTFluxRemoteModule.h"
#include "DTFluxRemoteModule.h"
#include "HttpServerModule.h"
#include "IHttpRouter.h"
#include "Json.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"));
// Auto-start server (optionnel)
StartHTTPServer(63350);
}
void UDTFluxRemoteSubsystem::Deinitialize()
{
StopHTTPServer();
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(" POST /dtflux/api/v1/title"));
UE_LOG(logDTFluxRemote, Log, TEXT(" POST /dtflux/api/v1/title-bib"));
UE_LOG(logDTFluxRemote, Log, TEXT(" POST /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::SetupRoutes()
{
if (!HttpRouter.IsValid())
{
return;
}
// Route: POST /dtflux/api/v1/title
TitleRouteHandle = HttpRouter->BindRoute(
FHttpPath(TEXT("/dtflux/api/v1/title")),
EHttpServerRequestVerbs::VERB_GET,
FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleTitleRequest)
);
// Route: POST /dtflux/api/v1/title-bib
TitleBibRouteHandle = HttpRouter->BindRoute(
FHttpPath(TEXT("/dtflux/api/v1/title-bib")),
EHttpServerRequestVerbs::VERB_GET,
FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleTitleBibRequest)
);
// Route: POST /dtflux/api/v1/commands
CommandsRouteHandle = HttpRouter->BindRoute(
FHttpPath(TEXT("/dtflux/api/v1/commands")),
EHttpServerRequestVerbs::VERB_GET,
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);
});
// Send success response
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);
});
OnComplete(FHttpServerResponse::Create(CreateSuccessResponse(TEXT("Command data received")), TEXT("application/json")));
return true;
}
TSharedPtr<FJsonObject> UDTFluxRemoteSubsystem::ParseJsonFromRequest(const FHttpServerRequest& Request)
{
// Get request body
TArray<uint8> Body = Request.Body;
FString 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())
{
return false;
}
// Parse title fields
JsonObject->TryGetStringField(TEXT("LastName"), OutData.LastName);
JsonObject->TryGetStringField(TEXT("FirsName"), OutData.FirstName);
JsonObject->TryGetStringField(TEXT("Function1"), OutData.Function1);
JsonObject->TryGetStringField(TEXT("Function2"), OutData.Function2);
UE_LOG(logDTFluxRemote, Log, TEXT("Parsed Title Data - LastName: %s, FirstName: %s"), *OutData.LastName, *OutData.FirstName);
return true;
}
bool UDTFluxRemoteSubsystem::ParseTitleBibData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteBibData& OutData)
{
if (!JsonObject.IsValid())
{
return false;
}
JsonObject->TryGetNumberField(TEXT("Bib"), OutData.Bib);
UE_LOG(logDTFluxRemote, Log, TEXT("Parsed Title-Bib Data - Bib: %i"), OutData.Bib);
return true;
}
bool UDTFluxRemoteSubsystem::ParseCommandData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteCommandData& OutData)
{
if (!JsonObject.IsValid())
{
return false;
}
JsonObject->TryGetNumberField(TEXT("type"), OutData.Type);
JsonObject->TryGetStringField(TEXT("Data"), OutData.Data);
UE_LOG(logDTFluxRemote, Log, TEXT("Parsed Command Data - Command Type: %i, Data: %s"),
OutData.Type, *OutData.Data);
return true;
}
// Manual processing functions for testing
bool UDTFluxRemoteSubsystem::ProcessTitleData(const FString& JsonString)
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
{
return false;
}
FDTFluxRemoteTitleData TitleData;
if (ParseTitleData(JsonObject, TitleData))
{
OnTitleReceived.Broadcast(TitleData);
return true;
}
return false;
}
bool UDTFluxRemoteSubsystem::ProcessTitleBibData(const FString& JsonString)
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
{
return false;
}
FDTFluxRemoteBibData TitleBibData;
if (ParseTitleBibData(JsonObject, TitleBibData))
{
OnTitleBibReceived.Broadcast(TitleBibData);
return true;
}
return false;
}
bool UDTFluxRemoteSubsystem::ProcessCommandData(const FString& JsonString)
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
{
return false;
}
FDTFluxRemoteCommandData CommandData;
if (ParseCommandData(JsonObject, CommandData))
{
OnCommandReceived.Broadcast(CommandData);
return true;
}
return false;
}

View File

@ -0,0 +1,75 @@
// 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();
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")
int Type = -1;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
FString Data = "";
FDTFluxRemoteCommandData(int InType, FString InData):
Type(InType), Data(InData){};
};

View File

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

View File

@ -0,0 +1,82 @@
// 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"
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:
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);
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;
int32 ServerPort = 63350;
bool bServerRunning = false;
FHttpRouteHandle TitleRouteHandle;
FHttpRouteHandle TitleBibRouteHandle;
FHttpRouteHandle CommandsRouteHandle;
};

View File

@ -22,6 +22,7 @@ public class DTFluxUtilities : ModuleRules
"SlateCore",
"DTFluxCore",
"DTFluxCoreSubsystem",
"AvalancheMedia",
}
);
}

View File

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

View File

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