// Fill out your copyright notice in the Description page of Project Settings. #include "Subsystems/DTFluxNetworkSubsystem.h" #include "DTFluxCoreModule.h" #include "DTFluxNetworkModule.h" #include "DTFluxNetworkSettings.h" #include "JsonObjectConverter.h" #include "Clients/DTFluxHttpClient.h" #include "Clients/DTFluxWebSocketClient.h" #include "Struct/DTFluxServerResponseStruct.h" #include "Struct/DTFluxRequestStructs.h" #include "Struct/DTFluxRaceDataServerResponse.h" #include "Struct/DTFluxRankingServerResponse.h" #include "Struct/DTFluxSplitSensorServerResponse.h" #include "Struct/DTFluxTeamListServerResponse.h" #include "Types/Objects/UDTFluxParticipantFactory.h" #include "Types/Struct/DTFluxRaceDataStructs.h" #include "Types/Struct/DTFluxSplitSensor.h" void UDTFluxNetworkSubsystem::Connect() { WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port)); WsClient->Connect(); } void UDTFluxNetworkSubsystem::Disconnect() { WsClient->Disconnect(); } void UDTFluxNetworkSubsystem::Reconnect() { ReconnectWs(FName("Ws_Client_0")); } void UDTFluxNetworkSubsystem::SendRequest(const EDTFluxRequestType RequestType, int InContestId, int InStageId, int InSplitId) { FString Message; switch (RequestType) { case EDTFluxRequestType::ContestRanking: FJsonObjectConverter::UStructToJsonObjectString(FDTFluxContestRankingRequest(InContestId), Message); break; case EDTFluxRequestType::StageRanking: FJsonObjectConverter::UStructToJsonObjectString(FDTFluxStageRankingRequest(InContestId, InStageId), Message); break; case EDTFluxRequestType::SplitRanking: FJsonObjectConverter::UStructToJsonObjectString(FDTFluxSplitRankingRequest(InContestId, InStageId, InSplitId), Message); break; case EDTFluxRequestType::TeamList: FJsonObjectConverter::UStructToJsonObjectString(FDTFluxTeamListRequest(), Message); break; case EDTFluxRequestType::RaceData: FJsonObjectConverter::UStructToJsonObjectString(FDTFluxRaceDataRequest(), Message); break; default: return; } //Dirty trick to fix Case Message = Message.Replace(TEXT("Id"),TEXT( "ID"), ESearchCase::CaseSensitive); UE_LOG(logDTFluxCore, Warning, TEXT("Sending Request %s"), *Message); SendMessage(Message); } void UDTFluxNetworkSubsystem::SendMessage(const FString& Message) { UE_LOG(logDTFluxCore, Warning, TEXT("Sending Message %s"), *Message); if(WsClient.IsValid() && WsClient->CanSend()) { WsClient->Send(Message); UE_LOG(logDTFluxNetwork, Log, TEXT("Can send request")); } else { UE_LOG(logDTFluxNetwork, Error, TEXT("[Websocket Not Connected]. Connect before sending requests...")); } } void UDTFluxNetworkSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); FDTFluxCoreModule& DTFluxCore = FModuleManager::Get().LoadModuleChecked("DTFluxCore"); FString StatusString = UEnum::GetValueAsString(WsStatus); UE_LOG(logDTFluxNetwork, Log, TEXT("Status is %s"), *StatusString); UDTFluxNetworkSettings* NetworkSettings = GetMutableDefault(); UDTFluxNetworkSettings::GetWebSocketSettings(NetworkSettings, WsSettings); UDTFluxNetworkSettings::GetHTTPSettings(NetworkSettings, HttpSettings); WsClient = MakeShareable(new FDTFluxWebSocketClient()); HttpClient = MakeShareable(new FDTFluxHttpClient()); RegisterWebSocketEvents(); RegisterHttpEvents(); #if WITH_EDITOR NetworkSettings->OnDTFluxWebSocketSettingsChanged.AddUFunction(this, FName("WsSettingsChanged")); NetworkSettings->OnDTFluxHttpSettingsChanged.AddUFunction(this, FName("HttpSettingsChanged")); #endif if(WsSettings.bShouldConnectAtStartup) { WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port)); WsClient->Connect(); } } void UDTFluxNetworkSubsystem::Deinitialize() { Super::Deinitialize(); } void UDTFluxNetworkSubsystem::WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings) { // TODO Implement a ClientSelector To retrieve impacted WsClients and populate changes or maybe create a delegate bool bNeedsReload = WsSettings != NewWsSettings; WsSettings = NewWsSettings; if( bNeedsReload || WsSettings.bShouldConnectAtStartup) { UE_LOG(logDTFluxNetwork, Warning, TEXT("WSocket Settings needs Reloding client")) ReconnectWs(FName("Ws_Client_0")); } } void UDTFluxNetworkSubsystem::HttpSettingsChanged(const FDTFluxHttpSettings& NewHttpSettings) { // TODO Implement a ClientSelector To retrieve impacted HttpClients and populate changes or maybe create a delegate HttpSettings = NewHttpSettings; } void UDTFluxNetworkSubsystem::ReconnectWs(const FName WsClientId) { FString NewAddress = ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port); WsClient->SetAddress(NewAddress); WsClient->Reconnect(); } void UDTFluxNetworkSubsystem::ReconnectHttp(const FName WsClientId) { } void UDTFluxNetworkSubsystem::RegisterWebSocketEvents() { OnWsConnectedEventDelegateHandle = WsClient->RegisterConnectedEvent().AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketConnected_Subsystem); OnWsConnectionErrorEventDelegateHandle = WsClient->RegisterConnectionError() .AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem); OnWsClosedEventDelegateHandle = WsClient->RegisterClosedEvent() .AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketClosedEvent_Subsystem); OnWsMessageEventDelegateHandle = WsClient->RegisterMessageEvent() .AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketMessageEvent_Subsystem); OnWsMessageSentEventDelegateHandle = WsClient->RegisterMessageSentEvent() .AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem); } void UDTFluxNetworkSubsystem::RegisterHttpEvents() { } void UDTFluxNetworkSubsystem::UnregisterWebSocketEvents() { if(OnWsConnectedEventDelegateHandle.IsValid()) { WsClient->UnregisterConnectedEvent().Remove(OnWsConnectedEventDelegateHandle); } if(OnWsConnectionErrorEventDelegateHandle.IsValid()) { WsClient->UnregisterConnectionError(); } if(OnWsClosedEventDelegateHandle.IsValid()) { WsClient->UnregisterClosedEvent(); } if(OnWsMessageEventDelegateHandle.IsValid()) { WsClient->UnregisterMessageEvent(); } if(OnWsMessageSentEventDelegateHandle.IsValid()) { WsClient->UnregisterRawMessageEvent(); } } void UDTFluxNetworkSubsystem::UnregisterHttpEvents() { } void UDTFluxNetworkSubsystem::OnWebSocketConnected_Subsystem() { WsStatus = EDTFluxConnectionStatus::Connected; OnWebSocketConnected.Broadcast(); UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws Is Connected with %s"), *WsClient->GetAddress()) } void UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem(const FString& Error) { UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws Error with %s : %s"), *WsClient->GetAddress(), *Error); WsStatus = EDTFluxConnectionStatus::Error; if(WsSettings.bShouldAutoReconnectOnError) { WsClient->Reconnect(); } } void UDTFluxNetworkSubsystem::OnWebSocketClosedEvent_Subsystem(int32 StatusCode, const FString& Reason, bool bWasClean) { UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws Error with %s :\n Reason : %s \tStatusCode : %i, bWasClean : %s"), *WsClient->GetAddress(), *Reason, StatusCode, bWasClean ? TEXT("True") : TEXT("False")); WsStatus = EDTFluxConnectionStatus::Closed; } void UDTFluxNetworkSubsystem::ParseTeamListResponse(const FDTFluxServerResponse& ServerResponse) { TSharedPtr JsonObject; TSharedRef> Reader = TJsonReaderFactory<>::Create(ServerResponse.RawMessage); if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid()) { UE_LOG(logDTFluxNetwork, Error, TEXT("JSON invalide : %s"), *ServerResponse.RawMessage); return; } const TArray>* DataArray; if (!JsonObject->TryGetArrayField(TEXT("datas"), DataArray)) { UE_LOG(logDTFluxNetwork, Error, TEXT("Aucun champ 'datas' trouvé dans le team-list")); return; } FDTFluxTeamListDefinition TeamListDefinition; for (const TSharedPtr& Value : *DataArray) { if (Value->Type == EJson::Object) { const TSharedPtr Item = Value->AsObject(); FDTFluxParticipant Participant; UDTFluxParticipantFactory::CreateFromJsonCpp(Item, Participant); TeamListDefinition.Participants.Add(Participant); } } UE_LOG(logDTFluxNetwork, Warning, TEXT("Inserting %i Participants [%s]"), TeamListDefinition.Participants.Num(), OnTeamListReceived.ExecuteIfBound(TeamListDefinition) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED")); } void UDTFluxNetworkSubsystem::ParseRaceData(const FDTFluxServerResponse& Response) { FDTFluxRaceDataResponse RaceData; if(FJsonObjectConverter::JsonObjectStringToUStruct(Response.RawMessage, &RaceData)) { //convert FDTFluxRaceData RaceDataDefinition; for(auto Contest : RaceData.Datas) { FDTFluxContest NewContest; NewContest.Name = Contest.Name; NewContest.ContestId = Contest.Id; NewContest.Date = Contest.Date; UE_LOG(logDTFluxNetwork, Warning, TEXT("Contest %i [%s] Starting at %s \nStages: \n"), Contest.Id, *Contest.Date.ToString(),*Contest.Name); for(auto Stage : Contest.Stages) { FDTFluxStage NewStage; NewStage.StageId = Stage.Id; NewStage.Name = Stage.Name; FString StartTimeFString = FString::Printf(TEXT("%s %s"), *NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")), *Stage.StartTime ); FString EndTimeFString = FString::Printf(TEXT("%s %s"), *NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")), *Stage.EndTime ); FString CutOffFString = FString::Printf(TEXT("%s %s"), *NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")), *Stage.CutOff ); FDateTime::Parse(StartTimeFString, NewStage.StartTime); FDateTime::Parse(EndTimeFString, NewStage.EndTime); FDateTime::Parse(CutOffFString, NewStage.CutOff); NewContest.Stages.Add(NewStage); UE_LOG(logDTFluxNetwork, Warning, TEXT("Stage %i [%s]: \nSTartTime Received [%s] -> Datetime[%s], CutOff [%s], EndTime [%s] \n"), Stage.Id, *Stage.Name, *Stage.StartTime, *NewStage.StartTime.ToString(), *NewStage.CutOff.ToString(), *NewStage.EndTime.ToString()); } NewContest.UpdateEndTime(); NewContest.UpdateLastStageId(); UE_LOG(logDTFluxNetwork, Warning, TEXT("Contest %i [%s]\nSplits: \n"), Contest.Id, *Contest.Name); for(auto Split: Contest.Splits) { FDTFluxSplit NewSplit; NewSplit.SplitId = Split.Id; NewSplit.Name = Split.Name; NewContest.Splits.Add(NewSplit); UE_LOG(logDTFluxNetwork, Warning, TEXT("Split %i [%s]: \n"), Split.Id, *Split.Name); } RaceDataDefinition.Datas.Add(NewContest); } UE_LOG(logDTFluxNetwork, Warning, TEXT("Sending %i Contests, [%s]"), RaceDataDefinition.Datas.Num(), OnRaceDataReceived.ExecuteIfBound(RaceDataDefinition) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED")); return; } UE_LOG(logDTFluxNetwork, Error, TEXT("ParseRaceData() for JSON Response : %s"), *Response.RawMessage); } void UDTFluxNetworkSubsystem::ParseContestRanking(const FDTFluxServerResponse& Response) { FDTFluxContestRankingResponse ContestRankingResponse; if(FJsonObjectConverter::JsonObjectStringToUStruct(Response.RawMessage, &ContestRankingResponse)) { FDTFluxContestRankings ContestRankings; ContestRankings.ContestId = ContestRankingResponse.ContestID; for(auto& RankingItem : ContestRankingResponse.Datas) { FDTFluxContestRanking Temp = RankingItem; ContestRankings.Rankings.Add(Temp); } UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws ContestRanking Data Sent for Contest %i, [%s]"), ContestRankings.ContestId, OnContestRankingReceived.ExecuteIfBound(ContestRankings) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED")); return; } UE_LOG(logDTFluxNetwork, Error, TEXT("ParseContestRanking() for JSON Response : %s"), *Response.RawMessage); } void UDTFluxNetworkSubsystem::ParseStageRankingResponse(const FDTFluxServerResponse& Response) { FDTFluxStageRankingResponse RankingResponse; if(FJsonObjectConverter::JsonObjectStringToUStruct(Response.RawMessage, &RankingResponse)) { FDTFluxStageRankings NewRankings; NewRankings.ContestId = Response.ContestID; NewRankings.StageId = Response.StageID; NewRankings.Rankings = static_cast>(RankingResponse.Datas); NewRankings.Initialize(); UE_LOG(logDTFluxNetwork, Warning, TEXT("StageRanking Data Sent for Contest %i, Stage %i\n[Result] : %s"), NewRankings.ContestId, NewRankings.StageId, OnStageRankingReceived.ExecuteIfBound(NewRankings) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED") ); return; } UE_LOG(logDTFluxNetwork, Error, TEXT("ParseStageRankingResponse() for JSON Response : %s"), *Response.RawMessage); } void UDTFluxNetworkSubsystem::ParseSplitRankingResponse(const FDTFluxServerResponse& Response) { FDTFluxSplitRankingResponse SplitRankingResponse; if(FJsonObjectConverter::JsonObjectStringToUStruct(Response.RawMessage, &SplitRankingResponse)) { FDTFluxSplitRankings NewSplitRankings; NewSplitRankings.ContestId = Response.ContestID; NewSplitRankings.StageId = Response.StageID; NewSplitRankings.SplitId = Response.SplitID; NewSplitRankings.Rankings = static_cast>(SplitRankingResponse.Datas); UE_LOG(logDTFluxNetwork, Warning, TEXT("SplitRanking Data Sent for Contest %i, Stage %i and Split %i\n[Result] : %s"), NewSplitRankings.ContestId, NewSplitRankings.StageId, NewSplitRankings.SplitId, OnSplitRankingReceived.ExecuteIfBound(NewSplitRankings) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED")); return; } UE_LOG(logDTFluxNetwork, Error, TEXT("ParseSplitRankingResponse() for JSON Response : %s"), *Response.RawMessage); } void UDTFluxNetworkSubsystem::ParseStatusUpdateResponse(const FDTFluxServerResponse& Response) { FDTFluxTeamStatusUpdate StatusUpdateResponse; if (FJsonObjectConverter::JsonObjectStringToUStruct(Response.RawMessage, &StatusUpdateResponse)) { UE_LOG(logDTFluxNetwork, Warning, TEXT("Status Update for bib %i \n[Result] : %s\n"), StatusUpdateResponse.Bib, OnTeamStatusUpdateReceived.ExecuteIfBound(StatusUpdateResponse) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED")); return; } UE_LOG(logDTFluxNetwork, Error, TEXT("ParseStatusUpdateResponse() for JSON Response : %s"), *Response.RawMessage); } void UDTFluxNetworkSubsystem::ParseSplitSensorResponse(const FDTFluxServerResponse& Response) { FDTFluxSplitSensorResponse SplitSensorResponse; if(FJsonObjectConverter::JsonObjectStringToUStruct(Response.RawMessage, &SplitSensorResponse)) { for(const auto& SplitSensorInfoResponse : SplitSensorResponse.Datas) { FDTFluxSplitSensorInfo NewSplitSensorInfo; NewSplitSensorInfo.Bib = SplitSensorInfoResponse.Bib; NewSplitSensorInfo.ContestId = SplitSensorInfoResponse.ContestID; NewSplitSensorInfo.StageId = SplitSensorInfoResponse.StageID; NewSplitSensorInfo.SplitId = SplitSensorInfoResponse.SplitID; NewSplitSensorInfo.Time = SplitSensorInfoResponse.Time; UE_LOG(logDTFluxNetwork, Warning, TEXT("Status Update for bib %i in Contest %i, Stage %i in split %i\n[Result] : %s\n"), NewSplitSensorInfo.Bib, NewSplitSensorInfo.ContestId, NewSplitSensorInfo.StageId, NewSplitSensorInfo.SplitId, OnSplitSensorReceived.ExecuteIfBound(NewSplitSensorInfo) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED")); } } UE_LOG(logDTFluxNetwork, Error, TEXT("ParseSplitSensorResponse() failed for JSON Response : %s"), *Response.RawMessage); } //TODO reforge API to keep track of Requests void UDTFluxNetworkSubsystem::OnWebSocketMessageEvent_Subsystem(const FString& MessageString) { UE_LOG(logDTFluxNetwork, Warning, TEXT("Client %s :\nMessage Received : %s"), *WsClient->GetAddress(), *MessageString); //Do Something With the message FDTFluxServerResponse Response; Response.ReceivedAt = FDateTime::Now(); Response.RawMessage = MessageString; Response.FailureReason = FText::FromString("--"); if(FJsonObjectConverter::JsonObjectStringToUStruct(MessageString, &Response, 0, 0, false, &(Response.FailureReason))) { if(Response.Code == -1) { // return DataReceived.Broadcast(Response); if(Response.Type.Contains("race-data")) { UE_LOG(logDTFluxNetwork, Warning, TEXT("Race-Data Received")); return ParseRaceData(Response); } if(Response.Type.Contains("team-list")) { UE_LOG(logDTFluxNetwork, Warning, TEXT("Team-List Received")); return ParseTeamListResponse(Response); } if(Response.Type.Contains("contest-ranking")) { UE_LOG(logDTFluxNetwork, Warning, TEXT("Contest-Ranking Received")); return ParseContestRanking(Response); } if(Response.Type.Contains("stage-ranking") ) { if(Response.SplitID == -1) { // StageRanking UE_LOG(logDTFluxNetwork, Warning, TEXT("Stage-Ranking Data")); ParseStageRankingResponse(Response); } else { // StageRanking UE_LOG(logDTFluxNetwork, Warning, TEXT("Split-Ranking Data")); return ParseSplitRankingResponse(Response); } } if(Response.Type.Contains("split-sensor")) { UE_LOG(logDTFluxNetwork, Warning, TEXT("split-sensor Data")); ParseSplitSensorResponse(Response); } if(Response.Type.Contains("status-update")) { UE_LOG(logDTFluxNetwork, Warning, TEXT("status-update Data")); ParseStatusUpdateResponse(Response); } if(Response.Type.Contains("team-update")) { UE_LOG(logDTFluxNetwork, Warning, TEXT("team-update Data")); ParseTeamListResponse(Response); } } } UE_LOG(logDTFluxNetwork, Error, TEXT("Ws %s :\nMessage Received : %s Cannot be Parsed"), *WsClient->GetAddress(), *MessageString); // return DataReceived.Broadcast(Response); } void UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent) { UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws %s :\nMessage Sent: %s"), *WsClient->GetAddress(), *MessageSent); } FString UDTFluxNetworkSubsystem::ConstructWsAddress(const FString& Address, const FString& Path, const int& Port) { FString NewAddress; if( !Address.Contains("ws://") && !Address.Contains("wss://")) { NewAddress += FString("ws://"); } NewAddress +=Address + FString(":") + FString::FromInt(Port) + Path; return NewAddress; // UE_LOG(logDTFluxNetwork, Log, TEXT("NewAddress : %s"), *NewAddress); }