Compare commits
1 Commits
d6b8874827
...
da89e35eb2
| Author | SHA1 | Date | |
|---|---|---|---|
| da89e35eb2 |
@ -49,6 +49,11 @@
|
|||||||
"Name": "DTFluxAPIStatus",
|
"Name": "DTFluxAPIStatus",
|
||||||
"Type": "Editor",
|
"Type": "Editor",
|
||||||
"LoadingPhase": "Default"
|
"LoadingPhase": "Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "DTFluxRemote",
|
||||||
|
"Type": "Editor",
|
||||||
|
"LoadingPhase": "Default"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Plugins": [
|
"Plugins": [
|
||||||
|
|||||||
@ -432,6 +432,7 @@ FGuid UDTFluxCoreSubsystem::InitContestRankingsDisplay(const int ContestId)
|
|||||||
// no need to request ContestRankings;
|
// no need to request ContestRankings;
|
||||||
if (IsContestRankingSealed(ContestId))
|
if (IsContestRankingSealed(ContestId))
|
||||||
{
|
{
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRankings already Sealed for ContestId %i"), ContestId);
|
||||||
const FGuid DisplayRequestId = FGuid::NewGuid();
|
const FGuid DisplayRequestId = FGuid::NewGuid();
|
||||||
OnContestRankingDisplayReady.Broadcast(DisplayRequestId, true);
|
OnContestRankingDisplayReady.Broadcast(DisplayRequestId, true);
|
||||||
return DisplayRequestId;
|
return DisplayRequestId;
|
||||||
@ -548,7 +549,7 @@ FGuid UDTFluxCoreSubsystem::InitSplitRankingsDisplay(const int ContestId, const
|
|||||||
EDTFluxApiDataType::SplitRanking, ContestId, StageId, SplitId, OnSuccess, OnError, true);
|
EDTFluxApiDataType::SplitRanking, ContestId, StageId, SplitId, OnSuccess, OnError, true);
|
||||||
return DisplayRequestId;
|
return DisplayRequestId;
|
||||||
}
|
}
|
||||||
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DTFluxDatastorage unavailable ..."));
|
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DTFluxDataStorage unavailable ..."));
|
||||||
OnSplitRankingDisplayReady.Broadcast(FGuid(), false);
|
OnSplitRankingDisplayReady.Broadcast(FGuid(), false);
|
||||||
return FGuid();
|
return FGuid();
|
||||||
}
|
}
|
||||||
@ -607,25 +608,7 @@ bool UDTFluxCoreSubsystem::GetSplitRankingForBib(const int ContestId, const int
|
|||||||
return false;
|
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,
|
bool UDTFluxCoreSubsystem::GetContestRankings(const int ContestId,
|
||||||
FDTFluxContestRankings& OutContestRankings)
|
FDTFluxContestRankings& OutContestRankings)
|
||||||
@ -638,8 +621,9 @@ bool UDTFluxCoreSubsystem::GetContestRankings(const int ContestId,
|
|||||||
if (NetworkSubsystem)
|
if (NetworkSubsystem)
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Requesting ContestRankings for ContestId %i"), ContestId);
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Requesting ContestRankings for ContestId %i"), ContestId);
|
||||||
TArray<int> TackedContestIds = {ContestId};
|
TArray<int> TrackedContestIds;
|
||||||
TrackedRequestContestRankings(TackedContestIds);
|
TrackedContestIds.Add(ContestId);
|
||||||
|
TrackedRequestContestRankings(TrackedContestIds);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("NetworkSubsystem unavailable"));
|
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("NetworkSubsystem unavailable"));
|
||||||
|
|||||||
@ -122,15 +122,14 @@ public:
|
|||||||
FGuid InitSplitRankingsDisplay(const int ContestId, const int StageId, const int SplitId);
|
FGuid InitSplitRankingsDisplay(const int ContestId, const int StageId, const int SplitId);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
bool GetStageRankingForBib(const int ContestId, const int StageId, const int Bib,
|
bool GetStageRankingForBib(const int ContestId, const int StageId, const int Bib,
|
||||||
FDTFluxStageRanking& OutStageRankings);
|
FDTFluxStageRanking& OutStageRankings);
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
bool GetSplitRankingForBib(const int ContestId, const int StageId, const int SplitId, const int Bib,
|
bool GetSplitRankingForBib(const int ContestId, const int StageId, const int SplitId, const int Bib,
|
||||||
FDTFluxSplitRanking& OutSplitRankings);
|
FDTFluxSplitRanking& OutSplitRankings);
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
|
||||||
bool GetContestRanking(const int ContestId, FDTFluxContestRanking& OutContestRanking);
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
bool GetContestRankings(const int ContestId, FDTFluxContestRankings& OutContestRankings);
|
bool GetContestRankings(const int ContestId, FDTFluxContestRankings& OutContestRankings);
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
|||||||
29
Source/DTFluxRemote/DTFluxRemote.Build.cs
Normal file
29
Source/DTFluxRemote/DTFluxRemote.Build.cs
Normal 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",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
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)
|
||||||
376
Source/DTFluxRemote/Private/DTFluxRemoteSubsystem.cpp
Normal file
376
Source/DTFluxRemote/Private/DTFluxRemoteSubsystem.cpp
Normal 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;
|
||||||
|
}
|
||||||
75
Source/DTFluxRemote/Public/DTFluxRemoteMessage.h
Normal file
75
Source/DTFluxRemote/Public/DTFluxRemoteMessage.h
Normal 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){};
|
||||||
|
};
|
||||||
|
|
||||||
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;
|
||||||
|
};
|
||||||
82
Source/DTFluxRemote/Public/DTFluxRemoteSubsystem.h
Normal file
82
Source/DTFluxRemote/Public/DTFluxRemoteSubsystem.h
Normal 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;
|
||||||
|
};
|
||||||
@ -22,6 +22,7 @@ public class DTFluxUtilities : ModuleRules
|
|||||||
"SlateCore",
|
"SlateCore",
|
||||||
"DTFluxCore",
|
"DTFluxCore",
|
||||||
"DTFluxCoreSubsystem",
|
"DTFluxCoreSubsystem",
|
||||||
|
"AvalancheMedia",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
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();
|
||||||
|
}
|
||||||
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