First DTFlux Subsystem Commit

This commit is contained in:
2024-07-05 15:16:31 +02:00
parent e5ed930002
commit 6146e5a9c6
15 changed files with 2237 additions and 819 deletions

View File

@ -1,3 +1,4 @@
[CoreRedirects]
+ClassRedirects=(OldName="/Script/DTFluxAPI.DTHttpServerObject",NewName="/Script/DTFluxAPI.DTFluxHttpServerObject")
+StructRedirects=(OldName="/Script/DTFluxAPI.DTHttpServerParams",NewName="/Script/DTFluxAPI.DTFluxHttpServerParams")
+StructRedirects=(OldName="/Script/DTFluxAPI.DTHttpServerParams",NewName="/Script/DTFluxAPI.DTFluxHttpServerParams")
+ClassRedirects=(OldName="/Script/DTFluxAPI.MyClass",NewName="/Script/DTFluxAPI.DTFluxDataStorage")

75
DOCS.md Normal file
View File

@ -0,0 +1,75 @@
# DTFluxAPI plugin doc
## DESCRIPTION
This document describe the usage of the plugin.
## USAGE
### Plugin Project Settings
#### Race Result API
- Race Result Port (int) : Port of the Race Result API (Local Server ONLY)
- Race Result Url (FString) : Race Result API URL (Local Server ONLY)
- Access Is Local (bool) : Race Result API only in local Mode (prevent polling limits)
- RaceResultSessionID (FString) : Race result session ID
- Start List Access Token (FString) : Token to access Start List endpoint
- General Classification Access Token (FString) : Token to access General Classification endpoint
- Live Stage Results Access Token (FString) : Token to access Live Stage Results endpoint
#### Chrono Proxy
- ProxyAddress
- ProxyRootPath
- ProxyPort
#### Server Config
- InPort (int) : Listening port of the embedded server
- Endpoints (FString[] ) : Array of endpoints to be served (Wil be modified in the future)
#### Objects provided
- EDTFluxProxyRoute : Routes type for the Proxy
- EDTFluxAPIRoute : Routes type for the API Race Result
- FSearchFilters : struct that contain a ContestId, a StageId and a gender to filter api searches
#### Functions provided
All the function provided by the Project Settings are available both in blueprint and in c++
- FString GetAPIPath(APIRouteType, FString Filters )
- GetAPIPathFiltered(APIRouteType, FSearchFilters Filters )
- GetProxyPath( ProxyRouteType, int ContestId, int StageId)
### Model
### DataStorage
#### Public Data
##### Collections
###### Chrono
- Chrono of each stage (count down)
- Current Stage and current contest
###### Participant
- Current progression (pourcentage done in stage -> number of checkpoints done)
-
### Subsystem
##### Delegates :
-
-

View File

@ -11,7 +11,7 @@ public class DTFluxAPI : ModuleRules
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core"
"Core",
}
);
@ -24,6 +24,9 @@ public class DTFluxAPI : ModuleRules
"SlateCore",
"HTTPServer",
"HTTP",
"Networking",
"WebSockets",
"WebSocketNetworking",
"DeveloperToolSettings",
"DeveloperSettings",
"Json",

View File

@ -0,0 +1,331 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "DTFluxDataStorage/DTFluxDataStorage.h"
// #include "AsyncTreeDifferences.h"
#include "DTFluxAPILog.h"
#include "DTFluxModel/DTFluxModel.h"
bool UDTFluxDataStorage::UpdateDataStorage(const FString JsonPayload)
{
UE_LOG(LogDTFluxAPI, Log, TEXT("UDPATE DataStorage with data : %s"), *JsonPayload);
// Single json value (any of supported json types e.g.
// object with properties, array, bool) at top level of json
TSharedPtr<FJsonValue> JsonValue;
// Create a reader pointer to read the json data
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonPayload);
if (FJsonSerializer::Deserialize(Reader, JsonValue)) {
// Get the value of the json object by field name
TSharedPtr<FJsonObject> Json = JsonValue->AsObject();
FString Type = Json->GetStringField(TEXT("type"));
TArray<TSharedPtr<FJsonValue>> Datas = Json->GetArrayField(TEXT("datas"));
// UE_LOG(LogDTFluxAPI, Log, TEXT("DTFlux-Response-Type : %s"), *Type);
if(Type.Contains("race-datas"))
{
UE_LOG(LogDTFluxAPI, Log, TEXT("DTFlux-Response-Type : \"race-datas\""));
// Contests is empty;
if(Contests.Num() == 0)
{
FDTFluxContest Contest;
for(const auto& Data : Datas)
{
TSharedPtr<FJsonObject> ContestData = Data->AsObject();
Contest.Id = ContestData->GetIntegerField(TEXT("id"));
Contest.Name = ContestData->GetStringField(TEXT("name"));
Contest.SetDate(ContestData->GetStringField(TEXT("date")));
TArray<FDTFluxSplit> Splits;
TArray<TSharedPtr<FJsonValue>> SplitDatas = ContestData->GetArrayField(TEXT("splits"));
for(const auto& SplitData : SplitDatas)
{
FDTFluxSplit Split;
Split.Id = SplitData->AsObject()->GetIntegerField(TEXT("id"));
Split.Name = SplitData->AsObject()->GetStringField(TEXT("name"));
Splits.Add(Split);
}
TArray<TSharedPtr<FJsonValue>> StagesData = ContestData->GetArrayField(TEXT("stages"));
Contest.AddStage(StagesData, Splits);
}
}
}
else if(Type.Contains("team-list"))
{
UE_LOG(LogDTFluxAPI, Log, TEXT("DTFlux-Response-Type : \"team-list\""));
}
else if(Type.Contains("contest-ranking"))
{
int ContestId = Json->GetIntegerField(TEXT("contestID"));
UE_LOG(LogDTFluxAPI, Log, TEXT("DTFlux-Response-Type : \"contest-ranking\""));
}
else if(Type.Contains("stage-ranking"))
{
int ContestId = Json->GetIntegerField(TEXT("contestID"));
int StageId = Json->GetIntegerField(TEXT("stageID"));
int SplitID = -1;
if(Json->HasField(TEXT("splitID")))
{
SplitID = Json->GetIntegerField(TEXT("splitID"));
if(SplitID == -1)
{
// we have all splits gaps from the request
}
}
UE_LOG(LogDTFluxAPI, Log, TEXT("DTFlux-Response-Type : \"stage-ranking\""));
}else if(Type.Contains("split-sensor"))
{
UE_LOG(LogDTFluxAPI, Log, TEXT("split-sensor received"));
// Request New Ranking
//
}
}
return true;
}
TArray<FDTFluxStage> UDTFluxDataStorage::GetStages(const int ContestId)
{
TArray<FDTFluxStage> Stages;
for(const auto& Contest : Contests)
{
Stages.Append(Contest.Stages);
}
return Stages;
}
bool UDTFluxDataStorage::GetContest(FDTFluxContest& OutContest, const int& ContestId)
{
// Current contest requested
if(ContestId == -1)
{
FDateTime Now = FDateTime::Now();
for(auto& Contest : Contests)
{
for( auto& Stage : Contest.Stages)
{
if(Stage.StartTime >= Now && Stage.EndTime <= Now)
{
//We have a winner
OutContest = Contest;
return true;
}
}
}
}
else
{
for( auto& Contest : Contests)
{
if(Contest.Id == ContestId)
{
OutContest = Contest;
return true;
}
}
}
return false;
}
bool UDTFluxDataStorage::GetStage(FDTFluxStage& CurrentStage, const int& StageId)
{
// Current contest requested
if(StageId == -1)
{
FDateTime Now = FDateTime::Now();
for(auto& Contest : Contests)
{
for( auto& Stage : Contest.Stages)
{
if(StageId <= -1)
{
if(Stage.StartTime >= Now && Stage.EndTime <= Now)
{
//We have a winner for current stage
CurrentStage = Stage;
return true;
}
}else
{
if(Stage.Id == StageId)
{
//We have a winner for the search stage
CurrentStage = Stage;
return true;
}
}
}
}
}
return false;
}
TArray<FDTFluxParticipant> UDTFluxDataStorage::GetParticipants(const int ContestId)
{
TArray<FDTFluxParticipant> Participants;
for(const auto& Contest : Contests)
{
if (ContestId <= -1)
{
Participants.Append(Contest.Participants);
}
else if(ContestId == Contest.Id)
{
Participants.Append(Contest.Participants);
}
}
return Participants;
}
FDTFluxParticipant UDTFluxDataStorage::GetParticipant(const int ContestID, const int ParticipantBib)
{
return GetParticipants(ContestID)[ParticipantBib];
}
TArray<FDTFluxStageRanking> UDTFluxDataStorage::GetStageRanking(const int ContestId, const int StageId)
{
if(Contests.Num() > (ContestId -1))
{
FDTFluxContest Contest = Contests[ContestId - 1];
if(Contest.Stages.Num() > (StageId -1))
{
FDTFluxStage Stage = Contest.Stages[StageId - 1];
return Stage.StageRanking;
}
}
return TArray<FDTFluxStageRanking>();
}
void UDTFluxDataStorage::AddOrUpdateContest(const FDTFluxContestResponse& ContestResponse)
{
FDTFluxContest Contest;
bool NewContest = false;
if(!Contests.IsEmpty() )
{
for(auto& OldContest: Contests)
{
if(OldContest.Id == ContestResponse.Id)
{
Contest = OldContest;
NewContest = false;
break;
}else
{
NewContest = true;
}
}
}else
{
NewContest = true;
}
Contest.Id = ContestResponse.Id;
Contest.Name = ContestResponse.Name;
TArray<FDTFluxSplit> Splits;
for(auto Split: ContestResponse.Splits)
{
FDTFluxSplit S;
S.Id = Split.Id;
S.Name = Split.Name;
Splits.Add(S);
}
for(auto StageResp : ContestResponse.Stages )
{
FDTFluxStage Stage;
Stage.Id = StageResp.Id;
Stage.Name = StageResp.Name;
FDateTime::Parse(StageResp.StartTime, Stage.StartTime);
FDateTime::Parse(StageResp.EndTime, Stage.EndTime);
Stage.Splits = Splits;
Contest.Stages.Add(Stage);
}
if(NewContest)
{
Contests.Add(Contest);
}
// UE_LOG(LogDTFluxAPI, Log, TEXT("Contest DUMP %s"));
}
void UDTFluxDataStorage::AddOrUpdateParticipant(const FDTFluxTeamListItemResponse& TeamListItemResponse)
{
// UE_LOG(LogDTFluxAPI, Log, TEXT("About to process Participant %s BIB : %d"), *TeamListItemResponse.LastName, TeamListItemResponse.Bib);
FDTFluxParticipant Participant;
Participant.Bib = TeamListItemResponse.Bib;
Participant.Category = TeamListItemResponse.Category;
Participant.Club = TeamListItemResponse.Club;
Participant.Elite = TeamListItemResponse.Elite;
Participant.Person1.Gender = TeamListItemResponse.Gender;
Participant.Person1.FirstName = TeamListItemResponse.FirstName;
Participant.Person1.LastName = TeamListItemResponse.LastName;
// TODO ???
// Participant.Person2.Gender = TeamListItemResponse.Gender2;
Participant.Person2.FirstName = TeamListItemResponse.FirstName2;
Participant.Person2.LastName = TeamListItemResponse.LastName2;
Participant.Status = TeamListItemResponse.Status;
for(auto& Contest: Contests)
{
if(Contest.Id == TeamListItemResponse.ContestId)
{
Contest.AddParticipant(Participant);
return;
}
}
// UE_LOG(LogDTFluxAPI, Error, TEXT(" Participant %s with BIB : %d Has no valid Contest associated. Got %d. This participant wil not be registered."),
// *Participant.Person1.LastName, Participant.Bib, TeamListItemResponse.ContestId);
}
void UDTFluxDataStorage::AddSplitSensorResult(FDTFluxSplitSensorItemResponse Response)
{
// Send SplitSensor Result to BP
FDTFluxStage CurrentStage;
if(GetStage(CurrentStage, Response.StageID))
{
// this is an empty stage
if(CurrentStage.Id == -1 )
{
}
}
}
void UDTFluxDataStorage::GoToNextStage()
{
// If Number of stages is less or equal to the current stageID
if(IsInitialized())
{
if(Contests[CurrentContestId].Stages.Num() -1 <= CurrentStageId)
{
CurrentStageId += 1;
}else
{
ResetStageId();
ChangeCurrentContest();
}
}
}
void UDTFluxDataStorage::ChangeCurrentContest()
{
// Contest Are initialized
if(IsInitialized())
{
if(CurrentContestId < Contests.Num() -1)
{
// last Contest
CurrentContestId = 0;
}
}
}

View File

@ -2,3 +2,93 @@
#include "DTFluxModel/DTFluxModel.h"
void FDTFluxSplit::Dump() const
{
UE_LOG(LogDTFluxAPI, Log, TEXT("[DTFluxStage DUMP] Split ID : %i, Name:%s"), Id, *Name);
}
bool FDTFluxStage::SetStartTime(const FDateTime& ContestDate, const FString& TimeString)
{
TArray<FString> TimeTokensStart;
TimeString.ParseIntoArray(TimeTokensStart, TEXT(":"));
const int32 HoursStart = FCString::Atoi(*TimeTokensStart[0]);
const int32 MinutesStart = FCString::Atoi(*TimeTokensStart[1]);
const int32 SecondsStart = FCString::Atoi(*TimeTokensStart[2]);
StartTime = FDateTime(ContestDate.GetYear(), ContestDate.GetMonth(), ContestDate.GetDay(),
HoursStart, MinutesStart, SecondsStart);
UE_LOG(LogDTFluxAPI, Log, TEXT("Setting StartTime For %s to %s"), *Name, *StartTime.ToString());
return true;
}
bool FDTFluxStage::SetEndTime(const FDateTime& ContestDate, const FString& TimeString)
{
TArray<FString> TimeTokens;
TimeString.ParseIntoArray(TimeTokens, TEXT(":"));
const int32 Hours = FCString::Atoi(*TimeTokens[0]);
const int32 Minutes = FCString::Atoi(*TimeTokens[1]);
const int32 Seconds = FCString::Atoi(*TimeTokens[2]);
EndTime = FDateTime(ContestDate.GetYear(), ContestDate.GetMonth(), ContestDate.GetDay(),
Hours, Minutes, Seconds);
UE_LOG(LogDTFluxAPI, Log, TEXT("Setting EndTime For %s to %s"), *Name, *StartTime.ToString());
return true;
}
bool FDTFluxStage::UpdateStageRanking(TArray<TSharedPtr<FJsonValue>> Data)
{
return true;
}
bool FDTFluxStage::AddSplit(TArray<TSharedPtr<FJsonValue>> SplitData)
{
return true;
}
void FDTFluxStage::Dump() const
{
UE_LOG(LogDTFluxAPI, Log, TEXT("[DTFluxStage DUMP] Id : %i, Name : %s"), Id, *Name);
UE_LOG(LogDTFluxAPI, Log, TEXT("[DTFluxStage DUMP] StartTime : %s, EndTime : %s"), *StartTime.ToString(), *EndTime.ToString());
UE_LOG(LogDTFluxAPI, Log, TEXT("[DTFluxStage DUMP] Splits ["));
for(const auto& Split : Splits)
{
Split.Dump();
}
UE_LOG(LogDTFluxAPI, Log, TEXT("[DTFluxStage DUMP] ]"));
}
bool FDTFluxContest::AddStage( const TArray<TSharedPtr<FJsonValue>> StagesData, TArray<FDTFluxSplit> Splits)
{
for (const auto& StageData : StagesData)
{
FDTFluxStage Stage;
Stage.Id = StageData->AsObject()->GetIntegerField(TEXT("id"));
Stage.Name = StageData->AsObject()->GetStringField(TEXT("name"));
FString StartTime = StageData->AsObject()->GetStringField(TEXT("startTime"));
FString EndTime = StageData->AsObject()->GetStringField(TEXT("endTime"));
Stage.SetStartTime(Date, StartTime);
Stage.SetEndTime(Date, EndTime);
Stage.Splits = Splits;
Stages.Add(Stage);
Stage.Dump();
}
return true;
}
bool FDTFluxContest::SetDate(const FString& StringDate)
{
TArray<FString> Tokens;
StringDate.ParseIntoArray(Tokens, TEXT("-"));
if(Tokens.Num() != 3)
{
return false;
}
const int32 Year = FCString::Atoi(*Tokens[0]);
const int32 Month = FCString::Atoi(*Tokens[1]);
const int32 Day = FCString::Atoi(*Tokens[2]);
Date = FDateTime(Year, Month, Day);
return true;
}

View File

@ -3,71 +3,20 @@
#include "DTFluxProjectSettings/DTFluxProjectSettings.h"
FString UDTFluxProjectSettings::GetAPIPath(const TEnumAsByte<EDTFluxAPIRoute> RouteType, const FString& Filters) const
{
FString ApiAccessRoute;
UE_LOG(LogDTFluxAPI, Type::Log, TEXT("Filters in GetApiPath settings\nGot %s"), *Filters);
if (bAccessIsLocal)
{
// http://localhost/_246158/api/8O8JMI2739JS58R8KRJGJEZUSQXF807O
ApiAccessRoute += RaceResultPort != 80 ? FString::Printf(TEXT("%s:%i/_%s/api/"), *ProxyUrl, RaceResultPort, *APIToken) :
FString::Printf(TEXT("%s/_%s/api/"), *ProxyUrl, *APIToken);
}
else
{
// https://api.raceresult.com/246158/8O8JMI2739JS58R8KRJGJEZUSQXF807O
ApiAccessRoute += FString::Printf(TEXT("https://api.raceresult.com/%s/"), *APIToken);
}
switch(RouteType)
{
case (EDTFluxAPIRoute::Results):
return ApiAccessRoute + LiveStageResultsAccessToken + Filters;
break;
case (EDTFluxAPIRoute::FinalClassification):
return ApiAccessRoute + GeneralClassificationAccessToken + Filters;
break;
default:
return ApiAccessRoute + StartListAccessToken + Filters;
}
}
FString UDTFluxProjectSettings::GetAPIPathFiltered(const TEnumAsByte<EDTFluxAPIRoute> RouteType,
const FSearchFilters& Filters) const
{
const FString Filter = Filters.GetFilter();
UE_LOG(LogDTFluxAPI, Type::Log, TEXT("Filters in Project settings\nGot %s"), *Filter);
return GetAPIPath(RouteType, Filter );
}
FString UDTFluxProjectSettings::GetProxyPath(const TEnumAsByte<EDTFluxProxyRoute> RouteType,
const int& InContest, const int& InStage ) const
{
switch(RouteType)
{
case (EDTFluxProxyRoute::ProxyRankingContest):
return FString::Printf(TEXT("%s:%i%s/ranking/contest/%i/"), *ProxyAddress, ProxyPort, *ProxyRootPath, InContest);
break;
case (EDTFluxProxyRoute::ProxyRankingStage):
return FString::Printf(TEXT("%s:%i%s/ranking/contest/%i/stage?id=%i"),
*ProxyAddress, ProxyPort, *ProxyRootPath, InContest, InStage);
break;
case (EDTFluxProxyRoute::ProxyTeams):
return FString::Printf(TEXT("%s:%i%s/teams/"), *ProxyAddress, ProxyPort, *ProxyRootPath);
break;
default :
return FString::Printf(TEXT("%s:%i%s/race/datas/"), *ProxyAddress, ProxyPort, *ProxyRootPath);
break;
}
}
const UDTFluxProjectSettings* UDTFluxProjectSettings::GetDTFluxAPIProjectSettings()
{
return GetDefault<UDTFluxProjectSettings>();
}
void UDTFluxProjectSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
FString ChangeKey = PropertyChangedEvent.Property->GetName();
UE_LOG(LogDTFluxAPI, Log, TEXT("Settings %s has changed"), *ChangeKey);
OnProjectSettingsModified.Broadcast(ChangeKey, GetDTFluxAPIProjectSettings());
}
UDTFluxProjectSettings::UDTFluxProjectSettings()
{
SectionName = "DTFlux Settings";
CategoryName = "DTFlux Settings";
}

View File

@ -2,330 +2,536 @@
#include "DTFluxSubsystem/DTFluxSubsystem.h"
#include "DTFluxProjectSettings/DTFluxProjectSettings.h"
#include "DTFluxWebSocket/DTFluxWebsocketServer.h"
#include "DTFluxModel/DTFluxModel.h"
#include "HttpServerModule.h"
#include "HttpRouteHandle.h"
#include "DTFluxAPILog.h"
#include "DTFluxDataStorage/DTFluxDataStorage.h"
#include "IHttpRouter.h"
#include "HttpModule.h"
#include "JsonObjectConverter.h"
#include "Interfaces/IHttpResponse.h"
// TODO: Not implemented
bool UDTFluxSubsystem::OnRequest(const FHttpServerRequest& Request)
// DEPRECATED : Now in WS
FString FDTFluxSubsystemAPISettings::GetRaceDataEndpoint(const FDTFluxSubsystemAPISettings* Settings)
{
return true;
if(Settings)
{
FString RaceDataEndpoint =
FString::Printf(TEXT("%s/%p"), *Settings->GetProxyBaseEndpoint(), Settings->ProxyEndpoints.FindKey("race-datas"));
UE_LOG(LogDTFluxAPI, Log, TEXT("Proxy Race Data -> %s"), *RaceDataEndpoint);
return RaceDataEndpoint;
}
return FString("");
}
// TODO: Not implemented
void UDTFluxSubsystem::HandleRequest(const FString& Route,const FHttpServerRequest& Request, FHttpResultCallback OnComplete)
// DEPRECATED : Now in WS
FString FDTFluxSubsystemAPISettings::GetContestRankingEndpoint(const FDTFluxSubsystemAPISettings* Settings, const int ContestId)
{
// creating payload string
FString ReqPayload;
if (Request.Body.Num() > 0)
if(Settings)
{
const std::string RawBody((char*)Request.Body.GetData(), Request.Body.Num());
ReqPayload = UTF8_TO_TCHAR(RawBody.c_str());
FString Ranking = *Settings->ProxyEndpoints.FindKey("ranking");
const TCHAR* ContestIDTmpl = *FString("{:ContestID}");
const TCHAR* ContestIDValue = *FString(TEXT("%i"),ContestId);
FString ContestRanking = Ranking.Replace(ContestIDTmpl, ContestIDValue );
FString ContestRankingEndpoint = Settings->GetProxyBaseEndpoint() + ContestRanking;
UE_LOG(LogDTFluxAPI, Log, TEXT("Proxy Contest Ranking -> %s"), *ContestRankingEndpoint);
return ContestRankingEndpoint;
}
TSharedPtr<FJsonObject> JsonPayload;
if(!FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(ReqPayload), JsonPayload))
return FString("");
}
// DEPRECATED : Now in WS
FString FDTFluxSubsystemAPISettings::GetStageRankingEndpoint(const FDTFluxSubsystemAPISettings* Settings, const int ContestId,
const int StageId)
{
if(Settings)
{
UE_LOG(LogDTFluxAPI, Error, TEXT("unable to parse JSON Payload\n%s"), *ReqPayload);
FString StageRanking = GetContestRankingEndpoint(Settings, ContestId);
StageRanking = FString::Printf(TEXT("%s/stage/%i/"), *StageRanking, StageId);
UE_LOG(LogDTFluxAPI, Log, TEXT("Proxy Stage Ranking -> %s"), *StageRanking);
return StageRanking;
}
//Checking path
UE_LOG(LogDTFluxAPI,Log, TEXT("Request Received for Route %s"), *Route);
// EventStart
if(Route == TEXT("/event/start"))
{
FDTFluxStartStagePayload StartStagePayload;
FJsonObjectConverter::JsonObjectToUStruct<FDTFluxStartStagePayload>(JsonPayload.ToSharedRef(), &StartStagePayload, 0, 0);
OnEventStartReceived.Broadcast(StartStagePayload);
return FString("");
}
// DEPRECATED : Now in WS
FString FDTFluxSubsystemAPISettings::GetStageRankingFilteredEndpoint(const FDTFluxSubsystemAPISettings* Settings,
const int ContestId, const int StageId, const FString SplitName)
{
if (Settings){
FString StageRanking = GetStageRankingEndpoint(Settings, ContestId, StageId);
StageRanking = FString::Printf(TEXT("%s?splitname=%s"), *StageRanking, *SplitName);
UE_LOG(LogDTFluxAPI, Log, TEXT("Proxy Stage Ranking with Splitname -> %s"), *StageRanking);
return StageRanking;
}
else if(Route == TEXT("/team/create"))
{
// /team/create route
}
else if(Route == TEXT("/team/update"))
{
// /team/create route
} else if(Route == TEXT("/team/create"))
{
// /team/create route
}
// Default Route
else
{
}
//Preparing Response Header
TUniquePtr<FHttpServerResponse> Response = CreateHttpServerResponse();
// Adding Response Body
FDTFluxResponseBody RespBody;
RespBody.Success = TEXT("OK");
std::string RespBody_c;
RespBody_c = TCHAR_TO_UTF8(*RespBody.Deserialize());
Response->Body.Append((const uint8*)RespBody_c.c_str(), RespBody_c.length());
// Return the response
OnComplete(MoveTemp(Response));
return FString("");
}
void UDTFluxSubsystem::OnUpdateStartList(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
// DEPRECATED : Now in WS
FString FDTFluxSubsystemAPISettings::GetTeamsEndpoint(const FDTFluxSubsystemAPISettings* Settings)
{
if (!bWasSuccessful || !Response.IsValid())
if(Settings)
{
UE_LOG(LogDTFluxAPI, Error, TEXT("RaceResult Request failed"));
return;
FString TeamsEndpoint =
FString::Printf(TEXT("%s/%p"), *Settings->GetProxyBaseEndpoint(), Settings->ProxyEndpoints.FindKey("teams"));
UE_LOG(LogDTFluxAPI, Log, TEXT("Proxy Teams -> %s"), *TeamsEndpoint );
return TeamsEndpoint;
}
// Tricks because Payload root is an array
const FString ModifiedData = FString::Printf(TEXT("{\"Participants\":%s}"), *Response->GetContentAsString());
TSharedPtr<FJsonObject> Payload;
if(!FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(ModifiedData), Payload))
{
UE_LOG(LogDTFluxAPI, Error, TEXT("unable to parse JSON Payload****"));
}else
{
FDTFluxStartListPayload StartList;
if (FJsonObjectConverter::JsonObjectToUStruct<FDTFluxStartListPayload>(Payload.ToSharedRef(), &StartList, 0, 0))
{
UE_LOG(LogDTFluxAPI, Log, TEXT("Success Reading"))
for(const auto& Participant : StartList.Participants)
{
// Creating a new Contest
if(!Contests.Contests.Contains(Participant.ContestName))
{
FDTFluxContest NewContest;
NewContest.ContestID = Participant.ContestID;
NewContest.ContestName = Participant.ContestName;
Contests.Contests.Add(Participant.ContestName, NewContest);
}
FDTFluxContest* CurrentContest = Contests.Contests.Find(Participant.ContestName);
FDTFluxTeam Team;
Team.Bib = Participant.Bib;
FDTFluxParticipant ContestParticipant;
ContestParticipant.FirstName = Participant.FirstName;
ContestParticipant.LastName = Participant.LastName;
ContestParticipant.Gender = Participant.Gender;
ContestParticipant.Club = Participant.Club;
ContestParticipant.Category = Participant.Category;
Team.Participants.Add(ContestParticipant);
// if we have LastName2 it is a two personne Team
if(Participant.LastName2 != "")
{
FDTFluxParticipant ContestParticipant2;
ContestParticipant2.FirstName = Participant.FirstName2;
ContestParticipant2.LastName = Participant.LastName2;
ContestParticipant2.Gender = Participant.Gender2;
ContestParticipant2.Club = Participant.Club2;
ContestParticipant2.Category = Participant.Category;
Team.Participants.Add(ContestParticipant);
Team.TeamName = Participant.TeamName;
UE_LOG(LogDTFluxAPI, Log, TEXT("Participant is a team : TeamName \"%s\" "), *Team.TeamName);
}
else
{
Team.TeamName = Participant.FirstName + TEXT(" ") + Participant.LastName.ToUpper();
}
// check if Participant already exists
if(!CurrentContest->TeamAlreadyExist(Team))
{
// Add it to the TeamList
CurrentContest->AddTeam(Team);
UE_LOG(LogDTFluxAPI, Log, TEXT("Adding Team \"%s\" "), *Team.TeamName);
}
}
UE_LOG(LogDTFluxAPI, Log, TEXT("Contest has now %i elements"), Contests.Contests.Num());
}
else
{
UE_LOG(LogDTFluxAPI, Error, TEXT("Error Deserializing Payload \n*%s*\n"), *ModifiedData);
}
}
for(const auto& Element : Contests.Contests)
{
UE_LOG(LogDTFluxAPI, Log, TEXT("Contest %s"), *Element.Key);
FDTFluxContest Contest = Element.Value;
UE_LOG(LogDTFluxAPI, Log, TEXT("Number of participants in this contest %i"), Contest.TeamParticipants.Num());
}
return FString("");
}
TUniquePtr<FHttpServerResponse> UDTFluxSubsystem::CreateHttpServerResponse() const
{
// Create a Response to be returned by the server
TUniquePtr<FHttpServerResponse> Response = MakeUnique<FHttpServerResponse>();
Response->Code = EHttpServerResponseCodes::Ok;
// Response Header
Response->Headers.Add(TEXT("Content-Type"), { TEXT("application/json;charset=utf-8") });
Response->Headers.Add(TEXT("Access-Control-Allow-Origin"), { TEXT("*") });
Response->Headers.Add(TEXT("Access-Control-Allow-Methods"), { TEXT("GET,POST,PUT,PATCH,DELETE,OPTIONS") });
Response->Headers.Add(TEXT("Access-Control-Allow-Headers"), { TEXT("Origin,X-Requested-With,Content-Type,Accept") });
Response->Headers.Add(TEXT("Access-Control-Max-Age"), { TEXT("600") });
Response->Headers.Add(TEXT("Access-Control-Allow-Credentials"), { TEXT("true") });
Response->Headers.Add(TEXT("Server"), { TEXT("UE 5.4.1 EMBBEDED") });
/****
* DTFlux subsystem
****/
return Response;
}
void UDTFluxSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
mSettings = UDTFluxProjectSettings::GetDTFluxAPIProjectSettings();
if(mSettings)
{
// Setting up request
HttpRequest = &FHttpModule::Get();
HttpRouter = FHttpServerModule::Get().GetHttpRouter(mSettings->InPort);
if(!HttpRouter)
{
UE_LOG(LogDTFluxAPI, Type::Error, TEXT("Invalid Http Router for port : %i"), mSettings->InPort)
}
// Setting Up Routes
for(const TArray<FString> Routes = mSettings->Endpoints; const FString& Route : Routes)
{
const FHttpRequestHandler OptionRequestHandler = FHttpRequestHandler::CreateLambda([this](const FHttpServerRequest &Request, const FHttpResultCallback& OnComplete)
{
OnComplete(CreateHttpServerResponse());
return true;
});
FHttpRouteHandle OptionRouteHandle = HttpRouter->BindRoute(Route, EHttpServerRequestVerbs::VERB_OPTIONS, OptionRequestHandler);
HttpMountedMap.Add(Route + TEXT("HTTPOption"), OptionRouteHandle);
const UDTFluxProjectSettings* Settings = GetSettings();
LoadConfig(Settings);
WsClient = NewObject<UDTFluxWebSocketClient>(this, UDTFluxWebSocketClient::StaticClass());
WsClient->OnConnectionConnected.AddDynamic(this, &UDTFluxSubsystem::WsConnected );
WsClient->OnConnectionClosed.AddDynamic(this, &UDTFluxSubsystem::WsConnectionClosed );
WsClient->OnConnectionError.AddDynamic(this, &UDTFluxSubsystem::WsConnectionError );
WsClient->OnReceivedMessage.AddDynamic(this, &UDTFluxSubsystem::WsReceivedMessage );
UE_LOG(LogDTFluxAPI, Log, TEXT("Trying to connect to %s:%i"), *SubSettings.WebsocketAddress, SubSettings.WebsocketPort);
WsClient->Connect(SubSettings.WebsocketAddress, SubSettings.WebsocketPort);
const FHttpRequestHandler RequestHandler = FHttpRequestHandler::CreateLambda([this, Route](const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete)
{
// Processing Request to Raw OnRequestReceived firing
FDTFluxHttpServerHeaders Headers;
for(const auto &Header: Request.Headers)
{
Headers.Headers.Add(Header.Key, FString::Join(Header.Value, TEXT(",")));
}
FDTFluxHttpServerParams Params;
Params.Params = Request.QueryParams;
FDTFluxHttpServerBody Payload;
Payload.ReqBody = Request.Body;
DataStorage = NewObject<UDTFluxDataStorage>();
FDateTime Now = FDateTime::Now();
FDateTime Send1Min = Now + FTimespan::FromMinutes(1);
UE_LOG(LogDTFluxAPI, Log, TEXT("TEST timer timeSpan Duration : %s"), *Send1Min.ToString());
// Raw broadcasting Received event
OnRequestReceived.Broadcast(Headers, Params, Payload);
HandleRequest(Route, Request, OnComplete);
return true;
});
// Binding Routes
FHttpRouteHandle RouteHandle = HttpRouter->BindRoute(Route, EHttpServerRequestVerbs::VERB_POST, RequestHandler);
HttpMountedMap.Add(Route, RouteHandle);
}
}
// SetTimerEvent( Send1Min );
// WsServer Event binding
}
void UDTFluxSubsystem::Deinitialize()
{
StopServer();
UE_LOG(LogDTFluxAPI, Log, TEXT("Route Num %i"), HttpMountedMap.Num());
for( auto const& Route: HttpMountedMap)
if(WsClient)
{
HttpRouter->UnbindRoute(Route.Value);
UE_LOG(LogDTFluxAPI, Log, TEXT("WsClient not null"));
}
HttpMountedMap.Empty();
HttpRouter.Reset();
else
UE_LOG(LogDTFluxAPI, Log, TEXT("WsClient has been GC'ed"));
Super::Deinitialize();
}
TArray<FString> UDTFluxSubsystem::GetMountedRoutes() const
bool UDTFluxSubsystem::ReloadSubsystem()
{
TArray<FString> Mounted = TArray<FString>();
for(const auto Route : HttpMountedMap)
return Reconnect();
}
bool UDTFluxSubsystem::Reconnect()
{
bool Result = WsClient->Close();
if(!WsClient->IsConnected())
return WsClient->Connect( SubSettings.WebsocketAddress, SubSettings.WebsocketPort);
return false;
}
void UDTFluxSubsystem::LoadConfig(const UDTFluxProjectSettings* Settings)
{
SubSettings.WebsocketPort = Settings->WebsocketServerPort;
SubSettings.WebsocketAddress = Settings->WebsocketServerAddress;
SubSettings.ProxyAddress = Settings->ProxyAddress;
SubSettings.ProxyPort = Settings->ProxyPort;
TMap<FString,FString> SettingsEndpoints;
SettingsEndpoints.Add(FString("race-data"), Settings->ProxyRaceDataEndpoint);
SettingsEndpoints.Add(FString("contest-ranking"), Settings->ProxyRankingEndpoint);
SettingsEndpoints.Add(FString("stage-ranking"), Settings->ProxyRankingEndpoint);
SettingsEndpoints.Add(FString("team-list"), Settings->ProxyTeamsEndpoint);
SubSettings.ProxyEndpoints = SettingsEndpoints;
}
// Get project Settings
const UDTFluxProjectSettings* UDTFluxSubsystem::GetSettings()
{
if(const UDTFluxProjectSettings* Settings = UDTFluxProjectSettings::GetDTFluxAPIProjectSettings())
return Settings;
else
{
Mounted.Add(Route.Key);
UE_LOG(LogDTFluxAPI, Error, TEXT("Unable to get DTFlux API settings"));
return nullptr;
}
return Mounted;
}
void UDTFluxSubsystem::StartServer()
// tick function
void UDTFluxSubsystem::Tick(float DeltaTime)
{
if(bIsListening){return ;}
FHttpServerModule::Get().StartAllListeners();
bIsListening = true;
OnServerListening.Broadcast();
}
TArray<FDTFluxTeam> UDTFluxSubsystem::GetParticipantsByContestId(const int ContestId)
{
if(Contests.Contests.Num() != 0)
if(Timer.Num() > 0)
{
FString ContestName = Contests.GetContestName(ContestId);
UE_LOG(LogDTFluxAPI, Log, TEXT("Getting Participants for Contest %s"), *ContestName);
return GetParticipantsByContestName(ContestName);
TArray<FDateTime> Done;
for(auto const& El : Timer)
{
FDateTime Dt = FDateTime::Now();
if(Dt >= El.Key)
{
El.Value.Execute(TEXT("Tick"));
OnTimerTriggered.Broadcast();
UE_LOG(LogDTFluxAPI, Log, TEXT("Execution"));
UE_LOG(LogDTFluxAPI, Log, TEXT("TICK : exec time: %lld == %lld"), El.Key.GetTicks(), Dt.GetTicks());
Done.Add(El.Key);
}
}
if(Done.Num() > 0)
{
UE_LOG(LogDTFluxAPI, Log, TEXT("TICK : Cleaning %i"), Done.Num());
for(auto const& ToDelete: Done)
{
Timer.Remove(ToDelete);
}
}
// UE_LOG(LogDTFluxAPI, Log, TEXT("TICK : Timer Length=%i"), Timer.Num());
}
}
// TODO: IMPLEMENT THIS METHOD
void UDTFluxSubsystem::WsSplitSensorReceivedInternal()
{
}
// TODO: IMPLEMENT THIS METHOD
void UDTFluxSubsystem::WsTeamUpdateReceivedInternal()
{
}
// TODO: IMPLEMENT THIS METHOD
void UDTFluxSubsystem::WsStatusUpdateReceivedInternal()
{
}
void UDTFluxSubsystem::RequestRaceDatas()
{
WsClient->SendMessage(TEXT("{\"path\": \"race-datas\"}"));
}
void UDTFluxSubsystem::RequestTeamList()
{
WsClient->SendMessage(TEXT("{\"path\": \"team-list\"}"));
}
void UDTFluxSubsystem::RequestContestRanking(const int ContestId)
{
const FString Request = FString::Printf(TEXT("{\"path\": \"contest-ranking\", \"contestID\" : %i}"), ContestId);
WsClient->SendMessage(Request);
}
void UDTFluxSubsystem::RequestStageRanking(const int ContestId, const int StageId)
{
const FString Request = FString::Printf(TEXT("{\"path\": \"stage-ranking\", \"contestID\" : %i, \"stageID\" : %i}"), ContestId, StageId);
WsClient->SendMessage(Request);
}
void UDTFluxSubsystem::RequestSplitGaps(const int ContestId, const int StageId, const int SplitId)
{
const FString Request =
FString::Printf(TEXT("{\"path\": \"stage-ranking\", \"contestID\" : %i, \"stageID\" : %i, \"splitID\" : %i}"),
ContestId, StageId, SplitId);
WsClient->SendMessage(Request);
}
void UDTFluxSubsystem::UpdateRaceData()
{
RequestRaceDatas();
}
void UDTFluxSubsystem::UpdateTeamList()
{
RequestTeamList();
}
void UDTFluxSubsystem::UpdateTeam()
{
}
void UDTFluxSubsystem::UpdateContestRanking(const int ContestID)
{
RequestContestRanking(ContestID);
}
void UDTFluxSubsystem::UpdateStageRanking(const int ContestID, const int StageID, const int SplitID)
{
if(SplitID == -1)
{
RequestStageRanking(ContestID, StageID);
}
else
{
UE_LOG(LogDTFluxAPI, Error, TEXT("No Contest Yet !!!!"));
TArray<FDTFluxTeam> EmptyTeam;
return EmptyTeam;
RequestSplitGaps(ContestID, StageID, SplitID);
}
}
TArray<FDTFluxTeam> UDTFluxSubsystem::GetParticipantsByContestName(const FString ContestName)
EDTFluxResponseType UDTFluxSubsystem::FindResponseType(const FString& MessageReceived)
{
if(Contests.Contests.Num() != 0)
EDTFluxResponseType ResponseType = UnknownResponse;
TSharedPtr<FJsonValue> JsonValue;
// Create a reader pointer to read the json data
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(MessageReceived);
if (FJsonSerializer::Deserialize(Reader, JsonValue))
{
return Contests.Contests[ContestName].TeamParticipants;
// Get the value of the json object by field name
TSharedPtr<FJsonObject> Json = JsonValue->AsObject();
//Test
FString Type = Json->GetStringField(TEXT("type"));
if(Type == "")
{
// UE_LOG(LogDTFluxAPI, Log, TEXT("Type tupe does not exist"));
return EDTFluxResponseType::UnknownResponse;
}
if(Type.Contains("race-datas"))
{
// TODO : check if object data are valid
FDTFluxRaceDataResponse RaceDataResponse;
if(!FJsonObjectConverter::JsonObjectToUStruct<FDTFluxRaceDataResponse>
(Json.ToSharedRef(), &RaceDataResponse))
{
UE_LOG(LogDTFluxAPI, Error, TEXT("Message %s is not a valid \"race-data\" object"), *MessageReceived);
return EDTFluxResponseType::UnknownResponse;
}
UE_LOG(LogDTFluxAPI, Log, TEXT("Message %s is valid race-data object"), *MessageReceived);
ProcessRaceDataResponse(RaceDataResponse);
return EDTFluxResponseType::RaceData;
}
if(Type.Contains("constest-ranking"))
{
FDTFluxContestRankingResponse ContestRankingResponse;
if(!FJsonObjectConverter::JsonObjectToUStruct<FDTFluxContestRankingResponse>
(Json.ToSharedRef(), &ContestRankingResponse))
{
UE_LOG(LogDTFluxAPI, Error, TEXT("Message %s is not a valid \"contest-ranking\" object"), *MessageReceived);
}
// TODO : check if object data are valid
UE_LOG(LogDTFluxAPI, Log, TEXT("Message %s is valid \"contest-ranking\" object"), *MessageReceived);
ProcessContestRankingResponse(ContestRankingResponse);
return EDTFluxResponseType::ContestRanking;
}
if(Type.Contains("stage-ranking"))
{
FDTFluxStageRankingResponse StageRankingResponse;
if(!FJsonObjectConverter::JsonObjectToUStruct<FDTFluxStageRankingResponse>
(Json.ToSharedRef(), &StageRankingResponse))
{
UE_LOG(LogDTFluxAPI, Error, TEXT("Message %s is not a valid \"stage-ranking\" object"), *MessageReceived);
}
UE_LOG(LogDTFluxAPI, Log, TEXT("Message %s is valid \"stage-ranking\" object"), *MessageReceived);
if(StageRankingResponse.SplitID == -1)
{
ProcessSplitRankingResponse(StageRankingResponse);
return EDTFluxResponseType::SplitRanking;
}
ProcessStageRankingResponse(StageRankingResponse);
return EDTFluxResponseType::StageRanking;
}
if(Type.Contains("team-list"))
{
FDTFluxTeamListResponse TeamListResponse;
if( !FJsonObjectConverter::JsonObjectToUStruct
<FDTFluxTeamListResponse>(Json.ToSharedRef(), &TeamListResponse))
{
UE_LOG(LogDTFluxAPI, Error, TEXT("Message %s is not a valid team-list object"), *MessageReceived)
return EDTFluxResponseType::UnknownResponse;
}
UE_LOG(LogDTFluxAPI, Log, TEXT("Received team-list data"));
ProcessTeamListResponse(TeamListResponse);
// TODO : check if object data are valid
return EDTFluxResponseType::TeamList;
}
if(Type.Contains("team-update"))
{
// TODO : check if object data are valid
return EDTFluxResponseType::TeamUpdate;
}
if(Type.Contains("split-sensor"))
{
// TODO : check if object data are valid
FDTFluxSplitSensorResponse SplitSensorResponse;
if( !FJsonObjectConverter::JsonObjectToUStruct
<FDTFluxSplitSensorResponse>(Json.ToSharedRef(), &SplitSensorResponse))
{
UE_LOG(LogDTFluxAPI, Error, TEXT("Message %s is not a valid split-sensor data"), *MessageReceived)
return EDTFluxResponseType::UnknownResponse;
}
UE_LOG(LogDTFluxAPI, Log, TEXT("Received split-sensor data"));
// send the array to DataStorage;
return EDTFluxResponseType::SplitSensor;
}
if(Type.Contains("status-update"))
{
// TODO : check if object data are valid
return EDTFluxResponseType::StatusUpdate;
}
}
else
return ResponseType;
}
/***
* Timer handling
***/
void UDTFluxSubsystem::BroadcastTimerEvent()
{
OnTimerTriggered.Broadcast();
UE_LOG(LogDTFluxAPI, Log, TEXT("TEST timer trigerred at : %s"), *FDateTime::Now().ToString());
}
void UDTFluxSubsystem::SetTimerEvent(const FDateTime& When)
{
FTimespan TimeSpan = FDateTime::Now() - When;
UE_LOG(LogDTFluxAPI, Log, TEXT("TEST timer timeSpan Duration : %s"), *TimeSpan.GetDuration().ToString());
}
bool UDTFluxSubsystem::AddTimer(FDateTime Time, FOnTimer NewTimer)
{
Timer.Add(Time, NewTimer);
return true;
}
/**
* END TIMER HANDLING
***/
void UDTFluxSubsystem::WsConnected()
{
OnWsConnected.Broadcast();
UE_LOG(LogDTFluxAPI, Log, TEXT("Ws Connected"));
}
void UDTFluxSubsystem::WsReceivedMessage( const FString& MessageReceived)
{
OnWsIncomingData.Broadcast(MessageReceived);
// UE_LOG(LogDTFluxAPI, Log, TEXT("Ws ReceivedMessage %s"), *MessageReceived);
// Find Data Object Type
EDTFluxResponseType Type = FindResponseType(MessageReceived);
switch(Type)
{
UE_LOG(LogDTFluxAPI, Error, TEXT("No Contest Yet !!!!"));
TArray<FDTFluxTeam> EmptyTeam;
return EmptyTeam;
case EDTFluxResponseType::TeamList:
break;
case EDTFluxResponseType::RaceData:
break;
case EDTFluxResponseType::ContestRanking:
break;
case EDTFluxResponseType::StageRanking:
break;
case EDTFluxResponseType::SplitRanking:
break;
case EDTFluxResponseType::TeamUpdate:
break;
default:
break;
}
// Let datastorage Know that we received something
// DataStorage->UpdateDataStorage(MessageReceived);
}
FString UDTFluxSubsystem::GetContestName(const int ContestId)
void UDTFluxSubsystem::WsConnectionClosed(const FString& Reason)
{
if(Contests.GetContestName(ContestId) != "")
OnWsClosed.Broadcast(Reason);
UE_LOG(LogDTFluxAPI, Log, TEXT("Ws ConnectionClosed with reason %s"), *Reason);
}
void UDTFluxSubsystem::WsConnectionError(const FString& Error)
{
OnWsError.Broadcast(Error);
UE_LOG(LogDTFluxAPI, Log, TEXT("Ws Error %s"), *Error);
}
bool UDTFluxSubsystem::IsConnected() const
{
return WsClient->IsConnected();
}
void UDTFluxSubsystem::ProcessTeamListResponse(const FDTFluxTeamListResponse& TeamListResponse)
{
for( const auto& TeamListItemResponse : TeamListResponse.Datas)
{
return Contests.GetContestName(ContestId);
UE_LOG(LogDTFluxAPI, Log, TEXT("Sending Participant %s with Bib %d to DataStorage"), *TeamListItemResponse.LastName, TeamListItemResponse.Bib);
DataStorage->AddOrUpdateParticipant(TeamListItemResponse);
}
return TEXT("");
for(auto& Contest : DataStorage->Contests)
{
Contest.DumpParticipant();
}
// UE_LOG(LogDTFluxAPI, Log, TEXT("New Particpant list Size %d"), DataStorage->GetParticipants().Num())
}
void UDTFluxSubsystem::UpdateStartList()
void UDTFluxSubsystem::ProcessRaceDataResponse(const FDTFluxRaceDataResponse& DataResponse)
{
const TSharedRef<IHttpRequest> Req = HttpRequest->CreateRequest();
Req->SetVerb("GET");
Req->SetURL(mSettings->GetAPIPath(EDTFluxAPIRoute::Starters));
Req->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
Req->OnProcessRequestComplete().BindUObject(this, &UDTFluxSubsystem::OnUpdateStartList);
for(const auto ContestResponse : DataResponse.Datas)
{
DataStorage->AddOrUpdateContest(ContestResponse);
}
UE_LOG(LogDTFluxAPI, Log, TEXT("New Contest Size %d"), DataStorage->Contests.Num())
Req->ProcessRequest();
}
void UDTFluxSubsystem::UpdateClassification(const int& ContestId, const int& StageId)
void UDTFluxSubsystem::ProcessContestRankingResponse(const FDTFluxContestRankingResponse& ContestRankingResponse)
{
TArray<FDTFluxContestRanking> NewRankings;
for(const auto& TeamContestRankingResponse : ContestRankingResponse.Datas)
{
FDTFluxContestRanking NewRankingEl;
NewRankingEl.Participant = DataStorage->GetParticipant(ContestRankingResponse.ContestID, TeamContestRankingResponse.Bib);
NewRankingEl.Rank = TeamContestRankingResponse.Rank;
NewRankingEl.Gap = TeamContestRankingResponse.Gap;
NewRankingEl.Time = TeamContestRankingResponse.Time;
NewRankings.Add(NewRankingEl);
}
}
void UDTFluxSubsystem::ProcessStageRankingResponse(const FDTFluxStageRankingResponse& StageRankingResponse)
{
}
TArray<FDTFluxParticipant> UDTFluxSubsystem::GetClassification(const int& ContestId, const int& StageId)
void UDTFluxSubsystem::ProcessSplitRankingResponse(const FDTFluxStageRankingResponse& SplitRankingResponse)
{
return TArray<FDTFluxParticipant>();
}
void UDTFluxSubsystem::StopServer()
void UDTFluxSubsystem::ProcessTeamUpdateResponse(const FDTFluxTeamUpdateResponse& TeamUpdateResponse)
{
FHttpServerModule::Get().StopAllListeners();
bIsListening = false;
OnServerStopped.Broadcast();
}
void UDTFluxSubsystem::ProcessStatusUpdateResponse(const FDTFluxTeamUpdateResponse& TeamUpdateResponse)
{
}
void UDTFluxSubsystem::ProcessSplitSensor(const FDTFluxSplitSensorResponse& SplitSensorResponse)
{
}
TSharedPtr<FJsonObject> UDTFluxSubsystem::GetData(EDTFluxResponseType Type, const FString& Message)
{
TSharedPtr<FJsonValue> JsonValue;
TSharedPtr<FJsonObject> Object;
// Create a reader pointer to read the json data
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Message);
if (FJsonSerializer::Deserialize(Reader, JsonValue))
{
}
return Object;
}

View File

@ -0,0 +1,120 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "DTFluxWebSocket/DTFluxWebsocketClient.h"
// Fill out your copyright notice in the Description page of Project Settings.
#include "DTFluxWebSocket/DTFluxWebsocketClient.h"
#include "IWebSocket.h"
#include "WebSocketsModule.h"
#include "DTFluxAPILog.h"
// UDTFluxWebSocketClient::~UDTFluxWebSocketClient()
// {
//
// }
void UDTFluxWebSocketClient::Initialize()
{
}
// void UDTFluxWebSocketClient::BeginDestroy()
// {
// // if(Ws->IsConnected())
// // Ws->Close();
// UObject::BeginDestroy();
// }
bool UDTFluxWebSocketClient::Connect(const FString URL, const int Port)
{
FString ServerUrl = FString::Printf(TEXT("%s:%i/"), *URL, Port);
UE_LOG(LogDTFluxAPI, Log, TEXT("[DTFluxWebsocketClient %s"), *ServerUrl);
Ws = FWebSocketsModule::Get().CreateWebSocket(ServerUrl);
Ws->OnConnected().AddLambda([this]()
{
if(IsValid(this) == false) return;
OnConnectionConnectedInternal();
});
Ws->OnClosed().AddLambda([this](int32 StatusCode, const FString& Reason, bool bWasClean)
{
if(IsValid(this) == false) return;
OnConnectionClosedInternal(Reason);
});
Ws->OnConnectionError().AddLambda([this](const FString& Error)
{
if(IsValid(this) == false) return;
OnConnectionErrorInternal(Error);
});
Ws->OnMessage().AddLambda([this](const FString& MessageString)
{
if(IsValid(this) == false) return;
OnReceivedMessageInternal(MessageString);
});
Ws->Connect();
if(Ws->IsConnected())
{
UE_LOG(LogDTFluxAPI, Log, TEXT("Listening"));
return true;
}
return false;
}
void UDTFluxWebSocketClient::Disconnect()
{
Ws->Close();
}
bool UDTFluxWebSocketClient::SendMessage(const FString Message, const bool Broadcast)
{
if(Ws->IsConnected() == false) return false;
Ws->Send(Message);
return true;
}
void UDTFluxWebSocketClient::OnConnectionConnectedInternal() const
{
OnConnectionConnected.Broadcast();
UE_LOG(LogDTFluxAPI, Log, TEXT("WebSocketsSubsystem, [Connected]"));
}
void UDTFluxWebSocketClient::OnConnectionErrorInternal(const FString& Error) const
{
OnConnectionError.Broadcast( Error);
UE_LOG(LogDTFluxAPI, Error, TEXT("WebSocketsSubsystem, [Error] : %s"), *Error);
}
void UDTFluxWebSocketClient::OnConnectionClosedInternal(const FString& Reason) const
{
OnConnectionClosed.Broadcast(Reason);
UE_LOG(LogDTFluxAPI, Log, TEXT("WebSocketsSubsystem, [Closed], Reason : %s"), *Reason);
}
void UDTFluxWebSocketClient::OnReceivedMessageInternal(const FString& Message) const
{
OnReceivedMessage.Broadcast(Message);
// UE_LOG(LogDTFluxAPI, Log, TEXT("WebSocketsSubsystem, [Message Reveived], Message : %s"), *Message);
}
bool UDTFluxWebSocketClient::Close() const
{
if (Ws->IsConnected())
Ws->Close();
return Ws->IsConnected();
}
bool UDTFluxWebSocketClient::IsConnected()
{
return Ws->IsConnected();
}

View File

@ -0,0 +1,246 @@
// // Fill out your copyright notice in the Description page of Project Settings.
//
//
// #include "DTFluxWebSocket/DTFluxWebsocketServer.h"
//
// #include <winsock2.h>
//
// #include "IWebSocket.h"
// #include "WebSocketsModule.h"
// #include "INetworkingWebSocket.h"
// #include "IWebSocketNetworkingModule.h"
// #include "WebSocketNetworkingDelegates.h"
// #include "DTFluxAPILog.h"
// #include "IWebSocketServer.h"
//
//
// UDTFluxWebsocketServer::UDTFluxWebsocketServer()
// {
// Ws = UDTFluxWebsocketServer::GetServer();
// }
//
// UDTFluxWebsocketServer::~UDTFluxWebsocketServer()
// {
//
// }
//
// bool UDTFluxWebsocketServer::ConnectToChannel(const FString URL, const int Port)
// {
// //try to get a WsServer
// if(!Ws)
// Ws = UDTFluxWebsocketServer::GetServer();
// if(Ws)
// {
// FWebSocketClientConnectedCallBack ClientConnectedCb;
// ClientConnectedCb.BindUObject(this, &UDTFluxWebsocketServer::OnConnectionConnectedInternal);
// if(Ws->Init((uint32) Port, ClientConnectedCb, FString("127.0.0.1")) )
// {
// UE_LOG(LogDTFluxAPI, Log, TEXT("Server listening on %s:%i"),*URL, Port);
// if (Ws.IsValid())
// UE_LOG(LogDTFluxAPI, Log, TEXT("Server pointer Is Valid %s"), *Ws->Info());
//
// return true;
// }
// else
// {
// UE_LOG(LogDTFluxAPI, Error, TEXT("Server Init() failed"));
// Ws.Reset();
// ClientConnectedCb.Unbind();
// return false;
// }
// }
// UE_LOG(LogDTFluxAPI, Error, TEXT("Unable to get a WsServer Object"));
//
// return false;
// }
//
// void UDTFluxWebsocketServer::LeaveChannel()
// {
// bIsConnected = false;
// Ws = nullptr;
// }
//
// bool UDTFluxWebsocketServer::SendMessageToAll(const FString Message)
// {
// if(!Ws)
// {
// UE_LOG(LogDTFluxAPI, Error, TEXT("WebSocketServer Not Up"));
// return false;
// }
// if(Clients.Num() <= 0)
// {
// UE_LOG(LogDTFluxAPI, Log, TEXT("No Clients Yet !!!"));
// return false;
// }
// int i = 0;
// for(const auto& Client : Clients)
// {
// const TCHAR* SerializedChar = Message.GetCharArray().GetData();
// int32 Size = FCString::Strlen(SerializedChar);
// int32 Sent = 0;
// uint8* Data = (uint8*)TCHAR_TO_UTF8(SerializedChar);
// if(Client->Send(Data, Size))
// {
// UE_LOG(LogDTFluxAPI, Log, TEXT("Message %s sent to client n°%i"), *Message, i);
// i++;
// }
// }
// if(i != 0)
// {
// UE_LOG(LogDTFluxAPI, Log, TEXT("%i Messages sent"), i);
// return true;
// }
// UE_LOG(LogDTFluxAPI, Error, TEXT("Message has not been sent"));
// return false;
// }
//
// void UDTFluxWebsocketServer::OnConnectionConnectedInternal(INetworkingWebSocket* ClientWebSocket)
// {
// OnConnectionConnected.Broadcast(ChannelName);
// FWebSocketPacketReceivedCallBack PacketReceivedCallBack;
// PacketReceivedCallBack.BindUObject(this, &UDTFluxWebsocketServer::OnReceivedMessageInternal);
// ClientWebSocket->SetReceiveCallBack(PacketReceivedCallBack);
// FWebSocketInfoCallBack InfoCallBack;
// InfoCallBack.BindLambda([this, ClientWebSocket]()
// {
// int i = 0;
//
// for(const auto& Client : Clients)
// {
// int Removed = Clients.Remove(Client);
// if(Removed)
// UE_LOG(LogDTFluxAPI, Log, TEXT("Client Disconnected %i"), i);
// i++;
// }
// });
// ClientWebSocket->SetSocketClosedCallBack(InfoCallBack);
// FWebSocketInfoCallBack InfoCallBackError;
// InfoCallBackError.BindUObject(this, &UDTFluxWebsocketServer::OnConnectionErrorInternal);
// ClientWebSocket->SetErrorCallBack(InfoCallBackError);
// UE_LOG(LogDTFluxAPI, Log, TEXT("WebSocketServer', [Connected], Channel: %s"), *ChannelName);
// }
//
// void UDTFluxWebsocketServer::OnConnectionErrorInternal()
// {
// UE_LOG(LogDTFluxAPI, Error, TEXT("WebSocketServer'"));
// OnConnectionError.Broadcast(TEXT("Channel"), TEXT("UNKNOWN ERROR"));
//
// }
//
// void UDTFluxWebsocketServer::OnReceivedMessageInternal(void* Data, int32 Count)
// {
// FString Message;
// if (Count == 0) // nothing to process
// {
// return;
// }
// const uint8* DataRef = reinterpret_cast<uint8*>(Data);
// const TArray<uint8> MessageData(DataRef, Count);
// const FString JSonData = UTF8_TO_TCHAR(MessageData.GetData());
// OnReceivedMessage.Broadcast(ChannelName, Message);
// UE_LOG(LogDTFluxAPI, Log, TEXT("WebSocketServer, [Message Reveived]\nMessage : %s"), *Message);
//
// }
//
// TUniquePtr<IWebSocketServer> UDTFluxWebsocketServer::GetServer()
// {
// return FModuleManager::Get().LoadModuleChecked<IWebSocketNetworkingModule>(TEXT("WebSocketNetworking")).
// CreateServer();
// }
//
// void UDTFluxWebsocketServer::Close()
// {
// UE_LOG(LogDTFluxAPI, Log, TEXT("WebSocketServer, [Closed()]"));
//
// }
#include "DTFluxWebSocket/DTFluxWebsocketServer.h"
//Fill out your copyright notice in the Description page of Project Settings.
#include "INetworkingWebSocket.h"
#include "IWebSocketNetworkingModule.h"
#include "WebSocketNetworkingDelegates.h"
#include "DTFluxAPILog.h"
UDTFluxServerWebSocket::UDTFluxServerWebSocket()
{
}
UDTFluxServerWebSocket::~UDTFluxServerWebSocket()
{
}
void UDTFluxServerWebSocket::Init(const int& Port, const FString& Url)
{
ServerWebSocket = FModuleManager::Get().LoadModuleChecked<IWebSocketNetworkingModule>(TEXT("WebSocketNetworking")).CreateServer();
FWebSocketClientConnectedCallBack CallBack;
CallBack.BindLambda([this](INetworkingWebSocket* Client)
{
FGuid uuid = FGuid::NewGuid();
ConnectedClients.Add(uuid, Client);
});
if (!ServerWebSocket->Init(Port, CallBack, Url))
{
UE_LOG(LogDTFluxAPI, Error, TEXT("ServerWebSocket Init FAIL"));
ServerWebSocket.Reset();
CallBack.Unbind();
return;
}
UE_LOG(LogDTFluxAPI, Log, TEXT("ServerWebSocket Connected to port %d"), Port);
TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([this](float Time)
{
if (ServerWebSocket)
{
ServerWebSocket->Tick();
return true;
}
else
{
return false;
}
}));
}
void UDTFluxServerWebSocket::BeginDestroy()
{
Super::BeginDestroy();
Close();
if (TickHandle.IsValid())
{
FTSTicker::GetCoreTicker().RemoveTicker(TickHandle);
TickHandle.Reset();
}
}
void UDTFluxServerWebSocket::OnWebSocketClientConnected(INetworkingWebSocket* ClientWebSocket)
{
FWebSocketPacketReceivedCallBack CallBack;
CallBack.BindUObject(this, &UDTFluxServerWebSocket::ReceivedRawPacket);
UE_LOG(LogDTFluxAPI, Log, TEXT("ServerWebSocket Connected"));
ClientWebSocket->SetReceiveCallBack(CallBack);
}
void UDTFluxServerWebSocket::ReceivedRawPacket(void* Data, int32 Count)
{
if (Count == 0) // nothing to process
{
return;
}
const uint8* DataRef = reinterpret_cast<uint8*>(Data);
const TArray<uint8> MessageData(DataRef, Count);
const FString JSonData = UTF8_TO_TCHAR(MessageData.GetData());
UE_LOG(LogDTFluxAPI, Log, TEXT("ServerWebSocket received %s"), *JSonData);
OnJsonRecieved.Broadcast(JSonData);
}

View File

@ -0,0 +1,96 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "DTFluxModel/DTFluxModel.h"
#include "UObject/Object.h"
#include "DTFluxDataStorage.generated.h"
/**
*
*/
struct FDTFluxStageRanking;
struct FDTFluxTeam;
struct FDTFluxParticipant;
struct FDTFluxStage;
struct FDTFluxContest;
UCLASS(BlueprintType, Category="DTFlux|Datastorage")
class DTFLUXAPI_API UDTFluxDataStorage : public UObject
{
GENERATED_BODY()
friend FDTFluxContest;
friend FDTFluxStage;
public:
UFUNCTION(BlueprintCallable, Category="DTFlux|DataStorage")
bool UpdateDataStorage(const FString JsonPayload);
UPROPERTY(BlueprintReadOnly, Category="DTFlux|DataStorage")
TArray<FDTFluxContest> Contests;
UPROPERTY(BlueprintReadOnly, Category="DTFlux|DataStorage")
int CurrentStageId = 0;
UPROPERTY(BlueprintReadOnly, Category="DTFlux|DataStorage")
int CurrentContestId = 0;
UFUNCTION(BlueprintCallable, Category="DTFlux|DataStorage")
int CurrentContest()
{
if(IsInitialized())
{
return CurrentContestId;
}
return -1;
};
UFUNCTION(BlueprintCallable, Category="DTFlux|DataStorage")
bool GetContest(FDTFluxContest& OutContest, const int& ContestId);
UFUNCTION(BlueprintCallable, Category="DTFlux|DataStorage")
TArray<FDTFluxStage> GetStages(const int ContestId);
UFUNCTION(BlueprintCallable, Category="DTFlux|DataStorage")
bool GetStage( FDTFluxStage& Stage,const int& StageId = -1);
UFUNCTION(BlueprintCallable, Category="DTFlux|DataStorage")
TArray<FDTFluxParticipant> GetParticipants(const int ContestId = -1);
UFUNCTION()
FDTFluxParticipant GetParticipant(const int ContestID, const int ParticipantBib);
UFUNCTION(BlueprintCallable, Category="DTFlux|DataStorage")
TArray<FDTFluxStageRanking> GetStageRanking(const int ContestId, const int StageId);
UFUNCTION(BlueprintType, Category="DTFlux|Datastorage")
void AddOrUpdateContest(const FDTFluxContestResponse& ContestResponse);
UFUNCTION(BlueprintType, Category="DTFlux|Datastorage")
void AddOrUpdateParticipant(const FDTFluxTeamListItemResponse& TeamListItemResponse);
UFUNCTION(BlueprintCallable, Category="DTFlux")
bool IsInitialized()
{
return Contests.Num() < 0;
}
UFUNCTION(BlueprintCallable, Category="DTFlux|Datastorage")
bool GetParticipantByBib(const int Bib, FDTFluxParticipant& OutParticipant)
{
for(auto& Contest : Contests)
{
if(Contest.GetParticipant(Bib, OutParticipant))
{
return true;
}
}
return false;
}
UFUNCTION(BlueprintCallable, Category="DTFlux")
void AddSplitSensorResult(FDTFluxSplitSensorItemResponse Response);
UFUNCTION(BlueprintCallable, Category="DTFlux")
void ResetStageId(){ CurrentStageId = 0; }
UFUNCTION(BlueprintCallable, Category="DTFlux")
void SetCurrentStage(int NewId){ CurrentStageId = NewId; }
UFUNCTION(BlueprintCallable, Category="DTFlux")
void GoToNextStage();
UFUNCTION(BlueprintCallable, Category="DTFlux")
void ChangeCurrentContest();
};

View File

@ -3,421 +3,488 @@
#pragma once
#include "CoreMinimal.h"
#include "DTFluxAPILog.h"
#include "UObject/Object.h"
#include "DTFluxModel.generated.h"
// Forward declarations
struct FDTFluxTeam;
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFLuxStartStageData
struct DTFLUXAPI_API FDTFluxPerson
{
GENERATED_BODY()
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Type;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Contest;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Stage;
// Maybe this one can be made DATETIME
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString RealStartTime;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxStartStagePayload
{
GENERATED_BODY()
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Description;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Trigger;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Type;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
TArray<FDTFLuxStartStageData> Datas;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxStartListItemPayload
{
GENERATED_BODY()
// "Report": "Start List",
// "ContestID": 0,
// "ContestName": "",
// "Bib": 1039,
// "Firstname": "Mystère",
// "Lastname": "Mystère",
// "Gender": "",
// "Club": "",
// "Firstname2": "",
// "Lastname2": "",
// "Gender2": "",
// "Club2": "",
// "TeamName": "",
// "Category": "",
// "Elite": false
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
int ContestID;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString ContestName;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
int Bib;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
public:
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
FString FirstName;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
FString LastName;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
FString Gender;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Club;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString FirstName2;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString LastName2;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Gender2;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Club2;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString TeamName;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Category;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
bool Elite;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
FString FunctionLine1 = TEXT("");
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
FString FunctionLine2 = TEXT("");
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxStartListPayload
{
GENERATED_BODY()
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
TArray<FDTFluxStartListItemPayload> Participants;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxParticipant
{
GENERATED_BODY()
public:
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString FirstName;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString LastName;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Club;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Gender;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
int Bib = -1;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
FDTFluxPerson Person1;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
FDTFluxPerson Person2;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
FString Category;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxTeam
{
GENERATED_BODY()
public:
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
TArray<FDTFluxParticipant> Participants;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString TeamName;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
int Bib;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct FDTFluxContest
{
GENERATED_BODY()
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
int ContestID;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString ContestName;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
TArray<FDTFluxTeam> TeamParticipants;
bool AddTeam(FDTFluxTeam Team)
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
FString Club;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
bool Elite;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
FString Status;
bool IsTeam()
{
TeamParticipants.Add(Team);
return true;
return (Person2.FirstName != "");
}
bool TeamAlreadyExist(FDTFluxTeam Team)
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxContestRanking
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
FDTFluxParticipant Participant;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
int Rank;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
FString Gap;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
FString Time;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxStageRanking
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
FDTFluxParticipant Participant;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
int Rank;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
FString Gap;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
FString TimeSwim;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
FString TimeTransition;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
FString TimeRun;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
FString TimeStart;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxSplitGapItem
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
FDTFluxParticipant Participant;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
int Gap;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxSplitGap
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
TArray<FDTFluxSplitGapItem> SplitGapItems;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxSplit
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
int Id = -1;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
FString Name;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
TArray<FDTFluxSplitGap> SplitGaps;
void Dump() const;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxStage
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
int Id;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
FString Name;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
FDateTime StartTime;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
FDateTime EndTime;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
TArray<FDTFluxSplit> Splits;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
TArray<FDTFluxStageRanking> StageRanking;
bool SetStartTime(const FDateTime& ContestDate, const FString& TimeString);
bool SetEndTime(const FDateTime& ContestDate, const FString& TimeString);
bool UpdateStageRanking(TArray<TSharedPtr<FJsonValue>> StageRankingData);
bool AddSplit(TArray<TSharedPtr<FJsonValue>> SplitData);
void Dump() const;
protected:
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxContest
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
int Id = -1;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
FString Name;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
TArray<FDTFluxParticipant> Participants;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
TArray<FDTFluxStage> Stages;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
FDateTime Date;
bool AddStage( const TArray<TSharedPtr<FJsonValue>> StagesData, const TArray<FDTFluxSplit> Splits);
bool SetDate(const FString& StringDate);
void AddParticipant(const FDTFluxParticipant& Participant)
{
for(const auto& MyTeam : TeamParticipants)
if(Participants.IsEmpty())
{
// Compare Bib
if(MyTeam.Bib == Team.Bib )
Participants.Add(Participant);
return;
}
int Index = 0;
FDTFluxParticipant ToUpdate;
bool Update = false;
for(auto P : Participants)
{
if(P.Bib == Participant.Bib)
{
ToUpdate = P;
Update = true;
break;
}
else
{
Index++;
}
}
if(Update)
{
Participants.RemoveAt(Index);
}
Participants.Add(Participant);
};
bool GetParticipant(const int Bib, FDTFluxParticipant& OutParticipant)
{
for (auto& Participant : Participants)
{
if(Participant.Bib == Bib)
{
OutParticipant = Participant;
return true;
}
}
return false;
}
void DumpParticipant()
{
int Num = 0;
for(const auto& Participant: Participants )
{
UE_LOG(LogDTFluxAPI, Log, TEXT("DUMP Participant : Name -> %s Bib %d"), *Participant.Person1.FirstName, Participant.Bib);
Num ++;
}
UE_LOG(LogDTFluxAPI, Log, TEXT("DUMP Participant : In Contest with ID %d there are %d Participant(s)"), Id, Num);
};
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxSplitSensorItemResponse
{
GENERATED_BODY()
UPROPERTY()
int Bib;
UPROPERTY()
FString Type = "split-sensor-item";
UPROPERTY()
int ContestID;
UPROPERTY()
int StageID;
UPROPERTY()
int SplitID;
UPROPERTY()
FString Time;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxSplitSensorResponse
{
GENERATED_BODY()
UPROPERTY()
FString Type = "split-sensor";
UPROPERTY()
TArray<FDTFluxSplitSensorItemResponse> Datas;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxTeamListItemResponse
{
GENERATED_BODY()
public:
UPROPERTY()
FString Type = "team-list-item";
UPROPERTY()
int ContestId;
UPROPERTY()
int Bib;
UPROPERTY()
FString FirstName;
UPROPERTY()
FString LastName;
UPROPERTY()
FString FirstName2 = "";
UPROPERTY()
FString LastName2 = "";
UPROPERTY()
FString Team = "";
UPROPERTY()
FString Gender;
UPROPERTY()
bool Elite;
UPROPERTY()
FString Category;
UPROPERTY()
FString Status;
UPROPERTY()
FString Club;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxContestList
struct DTFLUXAPI_API FDTFluxTeamListResponse
{
GENERATED_BODY()
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
TMap<FString, FDTFluxContest>Contests;
int GetContestId(const FString& ContestName) const
{
const FDTFluxContest* Contest = Contests.Find(ContestName);
if(Contest)
{
return Contest->ContestID;
}
return -1;
}
FString GetContestName(const int& ContestId) const
{
for (const auto& Contest : Contests)
{
if (Contest.Value.ContestID == ContestId)
{
return Contest.Key;
}
}
return FString();
}
public:
UPROPERTY()
FString Type = "team-list";
UPROPERTY()
TArray<FDTFluxTeamListItemResponse> Datas;
};
UCLASS(Blueprintable, Category="DTFlux|Model")
class DTFLUXAPI_API UDTFluxContestBPFn : public UBlueprintFunctionLibrary
USTRUCT()
struct DTFLUXAPI_API FDTFluxTeamUpdateResponse
{
GENERATED_BODY()
UPROPERTY()
FString Type = "team-update";
UPROPERTY()
TArray<FDTFluxTeamListItemResponse> Datas;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FStageResponse
{
GENERATED_BODY()
public:
UPROPERTY()
FString Type = "stage-response-data";
UPROPERTY()
int Id;
UPROPERTY()
FString Name;
UPROPERTY()
FString StartTime;
UPROPERTY()
FString EndTime;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FSplitResponse
{
GENERATED_BODY()
public:
UPROPERTY()
FString Type = "split-response-data";
UPROPERTY()
int Id;
UPROPERTY()
FString Name;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxContestResponse
{
GENERATED_BODY()
public:
UPROPERTY()
FString Type = "contest";
UPROPERTY()
int Id;
UPROPERTY()
FString Name;
UPROPERTY()
FString Date;
UPROPERTY()
TArray<FStageResponse> Stages;
UPROPERTY()
TArray<FSplitResponse> Splits;
};
USTRUCT()
struct DTFLUXAPI_API FDTFluxTeamContestRankingResponse
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, Category = "DT Http Server|Params")
static int GetContestId(const FDTFluxContestList& ContestList, const FString& ContestName)
{
return ContestList.GetContestId(ContestName);
}
UFUNCTION(BlueprintPure, Category = "DT Http Server|Params")
static FString GetContestName(const FDTFluxContestList& ContestList, const int& ContestId)
{
return ContestList.GetContestName(ContestId);
}
UPROPERTY()
FString Type = "team-contest-ranking";
UPROPERTY()
int Bib;
UPROPERTY()
int Rank;
UPROPERTY()
FString Time;
UPROPERTY();
FString Gap;
};
// TeamListItem Response from proxy Containing Team definition
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct FResponseTeamListItem
USTRUCT()
struct DTFLUXAPI_API FDTFluxContestRankingResponse
{
GENERATED_BODY()
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Contest;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
public:
UPROPERTY()
FString Type = "contest-ranking";
UPROPERTY()
int ContestID;
UPROPERTY()
TArray<FDTFluxTeamContestRankingResponse> Datas;
};
USTRUCT()
struct DTFLUXAPI_API FDTFluxTeamStageRankingResponse
{
GENERATED_BODY()
public:
UPROPERTY()
FString Type = "team-stage-ranking";
UPROPERTY()
int Bib;
UPROPERTY()
int Rank;
UPROPERTY()
FString Time;
UPROPERTY();
FString Gap;
UPROPERTY()
FString TimeSwim;
UPROPERTY();
FString TimeTransition;
UPROPERTY()
FString TimeRun;
UPROPERTY();
FString TimeStart;
};
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString FirstName;
USTRUCT()
struct DTFLUXAPI_API FDTFluxStageRankingResponse
{
GENERATED_BODY()
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString LastName;
public:
UPROPERTY()
FString Type = "stage-ranking";
UPROPERTY()
int ContestID;
UPROPERTY()
int StageID;
UPROPERTY()
int SplitID;
UPROPERTY()
TArray<FDTFluxTeamStageRankingResponse> Datas;
};
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Gender;
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXAPI_API FDTFluxRaceDataResponse
{
GENERATED_BODY()
public:
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
bool Elite;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Club;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Team;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Category;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
UPROPERTY()
FString Type = "race-datas";
UPROPERTY()
TArray<FDTFluxContestResponse> Datas;
};
USTRUCT()
struct DTFLUXAPI_API FDTFluxStatusTeamUpdateResponse
{
GENERATED_BODY()
UPROPERTY()
FString Type = "status-team-update";
UPROPERTY()
int Bib;
UPROPERTY()
FString Status;
};
// TeamList Response from proxy Containing List of Team
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct FProxyResponseTeamList
USTRUCT()
struct DTFLUXAPI_API FDTFluxStatusUpdateResponse
{
GENERATED_BODY()
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
TArray<FResponseTeamListItem> Datas;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct FProxyResponseStageListItem
{
GENERATED_BODY()
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
int Id;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Name;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString StartTime;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString EndTime;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct FProxyResponseSplitsListItem
{
GENERATED_BODY()
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
int Id;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Name;
UPROPERTY()
FString Type = "status-update";
TArray<FDTFluxStatusTeamUpdateResponse> Datas;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct FProxyResponseRaceListItem
UCLASS(BlueprintType, Category="DTFlux|Model|Helpers")
class DTFLUXAPI_API UDTFluxModelHelper : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
int Id;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Name;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Date;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
TArray<FProxyResponseStageListItem> Stages;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
TArray<FProxyResponseSplitsListItem> Splits;
public:
UFUNCTION(BlueprintCallable, Category="DTFlux|Model")
static bool IsParticipantATeam(const FDTFluxParticipant& Participant)
{
return Participant.Person2.FirstName != "";
}
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct FProxyResponseContestRankingListItem
{
GENERATED_BODY()
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
int Bib;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
int Rank;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Time;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Gap;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct FProxyResponseContestRankingList
{
GENERATED_BODY()
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
int ContestId;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
TArray<FProxyResponseContestRankingListItem> Datas;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct FProxyResponseStageRankingListItem
{
GENERATED_BODY()
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
int Bib;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
int Rank;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Time;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString Gap;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString TimeSwim;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString TimeTransition;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString TimeRun;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
FString TimeStart;
};
USTRUCT(BlueprintType, Category="DTFlux|Model")
struct FProxyResponseStageRankingList
{
GENERATED_BODY()
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
int ContestId;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
int StageId;
UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly)
TArray<FProxyResponseStageRankingListItem> Datas;
};

View File

@ -7,87 +7,11 @@
#include "DTFluxAPILog.h"
#include "DTFluxProjectSettings.generated.h"
UENUM()
enum EDTFluxAPIRoute: uint8
{
Root UMETA(DisplayName="API Root"),
Results UMETA(DisplayName="Results Route"),
Starters UMETA(DisplayName="Starters List Route"),
FinalClassification UMETA(DisplayName="Final Classification Route"),
DECLARE_MULTICAST_DELEGATE_TwoParams(OnDTFluxProjectSettingsModified, FString, const UDTFluxProjectSettings* );
};
UENUM()
enum EDTFluxProxyRoute : uint8
{
ProxyRaceData UMETA(DisplayName="Proxy Race Data"),
ProxyRankingContest UMETA(DisplayName="Proxy Ranking Contest Data"),
ProxyRankingStage UMETA(DisplayName="Proxy Ranking Stage Data"),
ProxyTeams UMETA(DisplayName="Proxy Teams Data"),
};
USTRUCT(BlueprintType)
struct FSearchFilters
{
GENERATED_BODY()
UPROPERTY(Blueprintable, Category="DTFlux|Config", EditAnywhere)
int ContestId = 0;
UPROPERTY(Blueprintable, Category="DTFlux|Config", EditAnywhere)
int StageId = 0;
UPROPERTY(Blueprintable, Category="DTFlux|Config", EditAnywhere)
int GenderId = 0;
FString GetFilter() const
{
if(ContestId == 0 && StageId == 0 && GenderId == 0)
{
UE_LOG(LogDTFluxAPI, Type::Log, TEXT("Gender Test -> %i"), GenderId);
return FString();
}
TArray<FString> Filters;
Filters.Add(FString("?"));
if (ContestId > 0)
{
Filters.Add(FString::Printf(TEXT("Contest=%i"), ContestId) );
}
if (StageId > 0)
{
Filters.Add(FString::Printf(TEXT("SelectorResult=%i"), StageId) );
}
if (GenderId > 0)
{
switch(GenderId)
{
case (2):
// default is male
UE_LOG(LogDTFluxAPI, Type::Log, TEXT("Gender Female -> %i"), GenderId);
Filters.Add(FString(TEXT("filter=[Gender]=\"f\"") ) );
break;
default:
// default is male
UE_LOG(LogDTFluxAPI, Type::Log, TEXT("Gender male -> %i"), GenderId);
Filters.Add(FString(TEXT("filter=[Gender]=\"m\"") ) );
break;
}
}
for(const auto& Element : Filters)
{
UE_LOG(LogDTFluxAPI, Type::Log, TEXT("Element : %s"), *Element);
}
FString FinalFilters = FString::Join(Filters, TEXT("&"));
FinalFilters.RemoveAt(1);
return FinalFilters;
};
};
/**
*
* DTFlux project settings
*/
UCLASS(Blueprintable, Config=Engine, DefaultConfig, meta=(DisplayName="DTFlux Project Settings"))
class DTFLUXAPI_API UDTFluxProjectSettings : public UDeveloperSettings
@ -96,21 +20,16 @@ class DTFLUXAPI_API UDTFluxProjectSettings : public UDeveloperSettings
public:
UPROPERTY(Category="DTFlux|Server Config", Config, EditAnywhere, BlueprintReadOnly)
int InPort = 8080;
OnDTFluxProjectSettingsModified OnProjectSettingsModified;
UPROPERTY(Category="DTFlux|Config|Race Result API", Config, EditAnywhere, BlueprintReadOnly)
int RaceResultPort = 80;
UPROPERTY(Category="DTFlux|Server Config", Config, EditAnywhere, BlueprintReadOnly)
TArray<FString> Endpoints;
UPROPERTY(Category="DTFlux|Config|Race Result API", Config, EditAnywhere, BlueprintReadOnly)
FString RaceResultUrl = "http://localhost";
UPROPERTY(Category="DTFlux|Config|Race Result API", Config, EditAnywhere, BlueprintReadOnly)
FString ProxyUrl = "http://localhost";
UPROPERTY(Category="DTFlux|Config|Race Result API", Config, EditAnywhere, BlueprintReadOnly)
FString APIToken;
FString RaceResultSessionID;
UPROPERTY(Category="DTFlux|Config|Race Result API", Config, EditAnywhere, BlueprintReadOnly)
bool bAccessIsLocal = true;
@ -123,28 +42,47 @@ public:
UPROPERTY(Category="DTFlux|Config|Race Result API", Config, EditAnywhere, BlueprintReadOnly)
FString LiveStageResultsAccessToken ;
// Proxy
UPROPERTY(Category="DTFlux|Config|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly)
int WebsocketServerPort = 3000;
UPROPERTY(Category="DTFlux|Config|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly)
FString WebsocketServerAddress = "ws://127.0.0.1";
UPROPERTY(Category="DTFlux|Config|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly)
FString ProxyAddress = "http://localhost";
UPROPERTY(Category="DTFlux|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly)
int ProxyPort = 8000;
UPROPERTY(Category="DTFlux|Config|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly)
FString ProxyRootPath = "/endpoints";
UPROPERTY(Category="DTFlux|Config|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly)
FString ProxyRaceDataEndpoint;
UPROPERTY(Category="DTFlux|Config|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly)
FString ProxyRankingEndpoint;
UPROPERTY(Category="DTFlux|Config|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly)
FString ProxyTeamsEndpoint;
UPROPERTY(Category="DTFlux|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly)
int ProxyPort = 8000;
//Server Config ****NOT USED****
UFUNCTION(BlueprintCallable, Category="DTFlux|Config")
FString GetAPIPath(const TEnumAsByte<EDTFluxAPIRoute> RouteType, const FString& Filters = TEXT("") ) const;
UFUNCTION(BlueprintCallable, Category="DTFlux|Config")
FString GetAPIPathFiltered(const TEnumAsByte<EDTFluxAPIRoute> RouteType, const FSearchFilters& Filters ) const;
UFUNCTION(BlueprintCallable, Category="DTFlux|Config")
FString GetProxyPath(const TEnumAsByte<EDTFluxProxyRoute> RouteType, const int& InContest = -1, const int& InStage = -1) const;
UPROPERTY(Category="DTFlux|Server Config", Config, EditAnywhere, BlueprintReadOnly)
int InPort = 8080;
UFUNCTION(BlueprintCallable, Category="DTFlux|Config")
static const UDTFluxProjectSettings* GetDTFluxAPIProjectSettings();
#if WITH_EDITOR
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
protected:

View File

@ -4,6 +4,8 @@
#include "CoreMinimal.h"
#include "Runtime/Engine/Public/Subsystems/EngineSubsystem.h"
#include "DTFluxWebSocket/DTFluxWebsocketServer.h"
#include "HttpServerRequest.h"
#include "HttpResultCallback.h"
#include "HttpRouteHandle.h"
@ -11,11 +13,13 @@
#include "DTFluxUtils/DTFluxHttpServerStruct.h"
#include "DTFluxAPILog.h"
#include "DTFluxDataStorage/DTFluxDataStorage.h"
#include "DTFluxModel/DTFluxModel.h"
#include "DTFluxWebSocket/DTFluxWebsocketClient.h"
#include "DTFluxSubsystem.generated.h"
class UDTFluxDataStorage;
class UDTFluxProjectSettings;
class IHttpRouter;
@ -35,6 +39,19 @@ enum EDTFluxResponseErrorCode
Internal_Error UMETA(DisplayName="Internal Server Error")
};
UENUM(BlueprintType, Category="DTFlux|Subsystem")
enum EDTFluxResponseType: uint8
{
UnknownResponse = 0 UMETA(DisplayName="UnknownResponse"),
RaceData = 1 UMETA(DisplayName="RaceData"),
ContestRanking = 2 UMETA(DisplayName="ContestRanking"),
StageRanking = 3 UMETA(DisplayName="StageRanking"),
SplitRanking = 4 UMETA(DisplayName="SplitRanking"),
TeamList = 5 UMETA(DisplayName="TeamList"),
TeamUpdate = 6 UMETA(DisplayName="TeamUpdate"),
SplitSensor = 7 UMETA(DisplayName="SplitSensor"),
StatusUpdate = 8 UMETA(DisplayName="StatusUpdate"),
};
USTRUCT(BlueprintType, Category="DTFlux|Server")
struct FDTFluxResponseBody
@ -62,92 +79,178 @@ FString Deserialize()
};
USTRUCT()
struct FDTFluxSubsystemAPISettings
{
GENERATED_BODY()
public:
FString WebsocketAddress = "ws://localhost";
int WebsocketPort = 3000;
FString ProxyAddress = "http://localhost";
int ProxyPort = 80;
//TODO : Maybe we must make a dedicated struct with enum to make endpoints more clean.
TMap<FString, FString> ProxyEndpoints;
static FString GetRaceDataEndpoint(const FDTFluxSubsystemAPISettings* Settings);
static FString GetContestRankingEndpoint(const FDTFluxSubsystemAPISettings* Settings, const int ContestId);
static FString GetStageRankingEndpoint(const FDTFluxSubsystemAPISettings* Settings, const int ContestId, const int StageId);
static FString GetStageRankingFilteredEndpoint(const FDTFluxSubsystemAPISettings* Settings, const int ContestId, const int StageId, const FString SplitName);
static FString GetTeamsEndpoint(const FDTFluxSubsystemAPISettings* Settings);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FDTFluxOnRequestReceived, FDTFluxHttpServerHeaders, HttpServerHeaders,
FDTFluxHttpServerParams, HttpServerParams, FDTFluxHttpServerBody, HttpRequestBody);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDTFluxOnServerListening);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDTFluxOnServerStopped);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDTFluxOnEventStartReceived, FDTFluxStartStagePayload, Payload);
private:
FString GetProxyBaseEndpoint() const
{
return FString::Printf(TEXT("%s:%i"), *ProxyAddress, ProxyPort);
};
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnWsConnected);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWsIncomingData, FString, JsonData);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWsError, FString, Error);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWsClosed, FString, Reason);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnTimerTriggered);
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnTimer, FString, TimerName);
/**
* DTFlux API Subsystem
*
* This Subsystem Mount HTTP routes to be listened and an HTTP poller to retrieve basic information.
* This Subsystem set up a Websocket server and Listen to incoming events and poll some http request to Proxy when needed.
* it handles a datastore where data are being saved and present to blueprint or actors.
*/
UCLASS()
class DTFLUXAPI_API UDTFluxSubsystem : public UEngineSubsystem
UCLASS(BlueprintType, Category="DTFlux|API Subsystem")
class DTFLUXAPI_API UDTFluxSubsystem : public UEngineSubsystem, public FTickableGameObject
{
GENERATED_BODY()
private:
bool bIsListening = false;
const UDTFluxProjectSettings* mSettings;
bool OnRequest(const FHttpServerRequest& Request);
static const UDTFluxProjectSettings* GetSettings();
int WebSocketPort = 0;
FDTFluxSubsystemAPISettings SubSettings;
void HandleRequest(const FString& Route, const FHttpServerRequest& Request, FHttpResultCallback OnComplete);
// // StartList Payload;
// FDTFluxStartListPayload StartList;
// Contest storage
FDTFluxContestList Contests;
void OnUpdateStartList(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
UPROPERTY()
UDTFluxWebSocketClient* WsClient;
UPROPERTY()
UDTFluxDataStorage* DataStorage;
virtual void Tick(float DeltaTime) override;
virtual bool IsTickableInEditor() const override
{
return true;
}
virtual TStatId GetStatId() const override
{
RETURN_QUICK_DECLARE_CYCLE_STAT(UDTFluxSubsystem, STATGROUP_Tickables);
}
protected:
UFUNCTION()
void WsSplitSensorReceivedInternal();
UFUNCTION()
void WsTeamUpdateReceivedInternal();
UFUNCTION()
void WsStatusUpdateReceivedInternal();
UFUNCTION()
void RequestRaceDatas();
UFUNCTION()
void RequestTeamList();
UFUNCTION()
void RequestContestRanking(const int ContestId);
UFUNCTION()
void RequestStageRanking(const int ContestId, const int StageId);
UFUNCTION()
void RequestSplitGaps(const int ContestId, const int StageId, const int SplitId);
UPROPERTY()
FDateTime TestTimer;
UFUNCTION()
void BroadcastTimerEvent();
UPROPERTY()
TMap<FDateTime, FOnTimer> Timer;
TMap<FString, FHttpRouteHandle> HttpMountedMap;
TSharedPtr<IHttpRouter> HttpRouter;
// Stop The Server
void StopServer();
// Create the server response
TUniquePtr<FHttpServerResponse> CreateHttpServerResponse() const;
FHttpModule* HttpRequest;
public:
/** Implement this for initialization of instances of the system */
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
/** Implement this for deinitialization of instances of the system */
virtual void Deinitialize() override;
UPROPERTY(BlueprintAssignable, Category="DTFlux|Subsystem|Events")
FDTFluxOnRequestReceived OnRequestReceived;
UPROPERTY(BlueprintAssignable, Category="DTFlux|Subsystem|Events")
FDTFluxOnServerListening OnServerListening;
UPROPERTY(BlueprintAssignable, Category="DTFlux|Subsystem|Events")
FDTFluxOnServerStopped OnServerStopped;
UPROPERTY(BlueprintAssignable, Category="DTFlux|Subsystem|Events")
FDTFluxOnEventStartReceived OnEventStartReceived;
UFUNCTION(BlueprintCallable, Category="DTFluxAPI | Subsytem")
bool ReloadSubsystem();
void LoadConfig(const UDTFluxProjectSettings* Settings);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Events")
FOnTimerTriggered OnTimerTriggered;
UPROPERTY(BlueprintAssignable, Category="DTFlux|Events")
FOnWsIncomingData OnWsIncomingData;
UPROPERTY(BlueprintAssignable, Category="DTFlux|Events")
FOnWsConnected OnWsConnected;
UPROPERTY(BlueprintAssignable, Category="DTFlux|Events")
FOnWsError OnWsError;
UPROPERTY(BlueprintAssignable, Category="DTFlux|Events")
FOnWsClosed OnWsClosed;
UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem|WebSocket")
bool Reconnect();
UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem|WebSocket")
bool AddTimer(FDateTime Time, FOnTimer NewTimer);
UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem")
TArray<FString> GetMountedRoutes() const;
void SetTimerEvent(const FDateTime& When);
UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem")
void StartServer();
UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem|Events")
TArray<FDTFluxTeam> GetParticipantsByContestId(const int ContestId );
void UpdateRaceData();
UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem")
void UpdateTeamList();
UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem")
void UpdateTeam();
UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem")
void UpdateContestRanking(const int ContestID);
UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem")
void UpdateStageRanking(const int ContestID, const int StageID, const int SplitID = -1);
UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem|Events")
TArray<FDTFluxTeam> GetParticipantsByContestName(const FString ContestName);
UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem")
UDTFluxDataStorage* GetDataStorage()
{
return DataStorage;
};
UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem|Events")
FString GetContestName(const int ContestId);
UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem|Race Result Call")
void UpdateStartList();
UFUNCTION()
void ProcessTeamListResponse(const FDTFluxTeamListResponse& TeamListResponse);
UFUNCTION()
void ProcessRaceDataResponse(const FDTFluxRaceDataResponse& DataResponse);
UFUNCTION()
void ProcessContestRankingResponse(const FDTFluxContestRankingResponse& ContestRankingResponse);
UFUNCTION()
void ProcessStageRankingResponse(const FDTFluxStageRankingResponse& StageRankingResponse);
UFUNCTION()
void ProcessSplitRankingResponse(const FDTFluxStageRankingResponse& StageRankingResponse);
UFUNCTION()
void ProcessTeamUpdateResponse(const FDTFluxTeamUpdateResponse& TeamUpdateResponse);
UFUNCTION()
void ProcessStatusUpdateResponse(const FDTFluxTeamUpdateResponse& TeamUpdateResponse);
UFUNCTION()
void ProcessSplitSensor(const FDTFluxSplitSensorResponse& SplitSensorResponse);
UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem|Race Result Call")
void UpdateClassification(const int& ContestId, const int& StageId = -1);
TSharedPtr<FJsonObject> GetData(EDTFluxResponseType Type, const FString& Message);
UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem|Race Result Call")
TArray<FDTFluxParticipant> GetClassification(const int& ContestId, const int& StageId = -1);
UFUNCTION()
EDTFluxResponseType FindResponseType(const FString& MessageReceived);
UFUNCTION()
void WsConnected();
UFUNCTION()
void WsReceivedMessage(const FString& MessageReceived);
UFUNCTION()
void WsConnectionClosed(const FString& Reason);
UFUNCTION()
void WsConnectionError(const FString& Error);
UFUNCTION(BlueprintCallable, Category="DTFlux|subsystem")
bool IsConnected() const;
};

View File

@ -0,0 +1,62 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "IWebSocket.h"
#include "UObject/Object.h"
#include "DTFluxWebsocketClient.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnConnectionConnected);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnConnectionClosed, const FString&, Reason);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnConnectionError, const FString&, Error);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedMessage, const FString&, Message);
UCLASS(Blueprintable, Category="DTFlux|Websocket")
class DTFLUXAPI_API UDTFluxWebSocketClient : public UObject
{
GENERATED_BODY()
public:
// UDTFluxWebSocketClient() = default;
void Initialize();
// virtual void BeginDestroy() override;
// virtual ~UDTFluxWebSocketClient() override;
UFUNCTION(BlueprintCallable, Category="DTFlux|Websocket")
bool Connect(const FString URL, const int Port );
UFUNCTION(BlueprintCallable, Category="DTFlux|Websocket")
void Disconnect();
UFUNCTION(BlueprintCallable, Category="DTFlux|Websocket")
bool SendMessage(const FString Message, const bool Broadcast = false);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Websocket")
FOnConnectionConnected OnConnectionConnected;
UPROPERTY(BlueprintAssignable, Category="DTFlux|Websocket")
FOnConnectionClosed OnConnectionClosed;
UPROPERTY(BlueprintAssignable, Category="DTFlux|Websocket")
FOnConnectionError OnConnectionError;
UPROPERTY(BlueprintAssignable, Category="DTFlux|Websocket")
FOnReceivedMessage OnReceivedMessage;
bool Close() const;
bool IsConnected();
protected:
private:
void OnConnectionConnectedInternal() const;
void OnConnectionErrorInternal(const FString& Error) const;
void OnConnectionClosedInternal(const FString& Reason) const;
void OnReceivedMessageInternal(const FString& Message) const;
TSharedPtr<IWebSocket> Ws;
};

View File

@ -0,0 +1,131 @@
// // Fill out your copyright notice in the Description page of Project Settings.
//
// #pragma once
//
// #include "CoreMinimal.h"
// #include "UObject/Object.h"
// #include "IWebSocketServer.h"
// #include "DTFluxWebsocketServer.generated.h"
//
//
//
// DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnConnectionConnected, const FString&, Channel);
// DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnConnectionClosed, const FString&, Channel, const FString&, Reason);
// DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnConnectionError, const FString&, Channel, const FString&, Error);
// DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnReceivedMessage, const FString&, Channel, const FString&, Message);
// class INetworkingWebSocket;
//
// UCLASS(Blueprintable, Category="DTFlux|Websocket")
// class DTFLUXAPI_API UDTFluxWebsocketServer : public UObject
// {
// GENERATED_BODY()
//
// public:
//
// UDTFluxWebsocketServer();
//
//
//
// void Initialize(const FString& Channel)
// {
// ChannelName = Channel;
// };
//
// virtual ~UDTFluxWebsocketServer() override;
//
// UFUNCTION(BlueprintCallable, Category="DTFlux|Websocket")
// bool ConnectToChannel(const FString URL, const int Port );
//
// UFUNCTION(BlueprintCallable, Category="DTFlux|Websocket")
// void LeaveChannel();
//
// UFUNCTION(BlueprintCallable, Category="DTFlux|Websocket")
// bool SendMessageToAll(const FString Message);
//
// UPROPERTY(BlueprintAssignable, Category="DTFlux|Websocket")
// FOnConnectionConnected OnConnectionConnected;
//
// UPROPERTY(BlueprintAssignable, Category="DTFlux|Websocket")
// FOnConnectionClosed OnConnectionClosed;
//
// UPROPERTY(BlueprintAssignable, Category="DTFlux|Websocket")
// FOnConnectionError OnConnectionError;
//
// UPROPERTY(BlueprintAssignable, Category="DTFlux|Websocket")
// FOnReceivedMessage OnReceivedMessage;
//
// UPROPERTY(BlueprintReadOnly, Category="DTFlux|Websocket")
// FString ChannelName;
//
// void Close();
//
//
// protected:
//
//
//
// private:
//
// bool bIsConnected = false;
// void OnConnectionConnectedInternal(INetworkingWebSocket* ClientWebSocket);
// void OnConnectionErrorInternal();
// // void OnConnectionClosedInternal(const FString& Reason);
// void OnReceivedMessageInternal(void* Data, int32 Count);
//
// static TUniquePtr<IWebSocketServer> GetServer();
//
// TArray<INetworkingWebSocket*> Clients;
// // TSharedPtr<IWebSocket> Ws;
// TUniquePtr<IWebSocketServer> Ws;
// };
#pragma once
#include "IWebSocketServer.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "DTFluxWebsocketServer.generated.h"
UCLASS(Blueprintable, Category="DTFlux|Websocket")
class DTFLUXAPI_API UDTFluxServerWebSocket : public UObject
{
GENERATED_BODY()
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnConnectionConnected);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnConnectionClosed, const FString&, Reason);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnConnectionError, const FString&, Error);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedMessage, const FString&, Message);
UDTFluxServerWebSocket();
virtual ~UDTFluxServerWebSocket() override;
void Init(const int& Port, const FString& Url = TEXT("ws://localhost"));
virtual void BeginDestroy() override;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnJsonRecieved, const FString&, Payload);
UPROPERTY(BlueprintAssignable)
FOnJsonRecieved OnJsonRecieved;
void Close()
{
ServerWebSocket = nullptr;
};
protected:
void OnWebSocketClientConnected(INetworkingWebSocket* ClientWebSocket); // to the server.
virtual void ReceivedRawPacket(void* Data, int32 Count);
private:
TUniquePtr<class IWebSocketServer> ServerWebSocket;
TMap<FGuid,INetworkingWebSocket*> ConnectedClients;
/** Delegate for callbacks to GameThreadTick */
FTSTicker::FDelegateHandle TickHandle;
};