// Fill out your copyright notice in the Description page of Project Settings. #include "DTFluxCoreSubsystem.h" #include "DTFluxCoreSubsystemModule.h" #include "DTFluxGeneralSettings.h" #include "DTFluxPursuitManager.h" #include "FileHelpers.h" #include "Assets/DTFluxModelAsset.h" #include "Subsystems/DTFluxNetworkSubsystem.h" #include "Struct/DTFluxServerResponseStruct.h" #include "UObject/SavePackage.h" void UDTFluxCoreSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("[UDTFluxCoreSubsystem] Initializing...")); if (!DataStorage) { const UDTFluxGeneralSettings* GeneralSettings = GetDefault(); TSoftObjectPtr ModelAsset = GeneralSettings->ModelAsset; DataStorage = ModelAsset.LoadSynchronous(); if (!DataStorage) { UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage Not Valid")); } } //TODO REMOVE This as it's only for testing purpose NetworkSubsystem = GEngine->GetEngineSubsystem(); if (NetworkSubsystem->WsStatus != EDTFluxConnectionStatus::Connected) { RegisterDelegates(); } PursuitManager = NewObject(this); } void UDTFluxCoreSubsystem::Deinitialize() { Super::Deinitialize(); } void UDTFluxCoreSubsystem::SaveDataStorage() { if (!DataStorage->MarkPackageDirty()) { UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Unable to make package dirty !!!")) } if (DataStorage) { UEditorLoadingAndSavingUtils::SavePackages({DataStorage->GetPackage()}, true); } } void UDTFluxCoreSubsystem::ProcessTrackedResponse(FDTFluxServerResponse& InResponse) { switch (InResponse.GetResponseType()) { case EDTFluxApiDataType::ContestRanking: { FDTFluxContestRankings Rankings; if (InResponse.ParseContestRanking(Rankings)) { OnContestRankings.Broadcast(Rankings.ContestId, Rankings); ProcessContestRanking(Rankings); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRanking added for Contest %s"), *Rankings.ContestName); } else { UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Unable to parse ContestRanking")); } break; } case EDTFluxApiDataType::StageRanking: { FDTFluxStageRankings Rankings; if (InResponse.ParseStageRanking(Rankings)) { FDTFluxStageKey StageKey(Rankings.ContestId, Rankings.StageId); OnStageRankings.Broadcast(StageKey, Rankings); ProcessStageRanking(Rankings); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRanking added for Stage %i of Contest %i"), Rankings.StageId, Rankings.ContestId); } else { UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Unable to parse StageRanking")); } break; } case EDTFluxApiDataType::SplitRanking: { FDTFluxSplitRankings Rankings; if (InResponse.ParseSplitRanking(Rankings)) { FDTFluxSplitKey SplitKey(Rankings.ContestId, Rankings.StageId, Rankings.SplitId); OnSplitRankings.Broadcast(SplitKey, Rankings); ProcessSplitRanking(Rankings); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRanking added for Split %i of Stage %i of Contest %i"), Rankings.SplitId, Rankings.StageId, Rankings.ContestId); } else { UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Unable to parse SplitRanking")); } break; } case EDTFluxApiDataType::RaceData: { FDTFluxRaceData RaceData; if (InResponse.ParseRaceData(RaceData)) { UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("RaceDataDefinition added for Contest %s"), *RaceData.Datas[0].Name); ProcessRaceData(RaceData); } break; } case EDTFluxApiDataType::TeamList: { FDTFluxTeamListDefinition TeamList; if (InResponse.ParseTeamList(TeamList)) { ProcessTeamList(TeamList); UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("Process TeamList")) } break; } default: UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Unknown DataType %s"), *UEnum::GetValueAsString(InResponse.GetResponseType())); break; } } void UDTFluxCoreSubsystem::RegisterDelegates() { if (NetworkSubsystem) { // ✅ Binding avec vérification automatique des signatures // Si la signature ne correspond pas, erreur de compilation ! NetworkSubsystem->OnReceivedRaceData().BindUObject( this, &UDTFluxCoreSubsystem::ProcessRaceData ); NetworkSubsystem->OnReceivedTeamList().BindUObject( this, &UDTFluxCoreSubsystem::ProcessTeamList ); NetworkSubsystem->OnReceivedContestRanking().BindUObject( this, &UDTFluxCoreSubsystem::ProcessContestRanking ); NetworkSubsystem->OnReceivedStageRanking().BindUObject( this, &UDTFluxCoreSubsystem::ProcessStageRanking ); NetworkSubsystem->OnReceivedSplitRanking().BindUObject( this, &UDTFluxCoreSubsystem::ProcessSplitRanking ); NetworkSubsystem->OnReceivedTeamStatusUpdate().BindUObject( this, &UDTFluxCoreSubsystem::ProcessTeamStatusUpdate ); NetworkSubsystem->OnReceivedTeamUpdate().BindUObject( this, &UDTFluxCoreSubsystem::ProcessTeamUpdate ); NetworkSubsystem->OnReceivedSplitSensor().BindUObject( this, &UDTFluxCoreSubsystem::ProcessSplitSensor ); } } void UDTFluxCoreSubsystem::ProcessRaceData(const FDTFluxRaceData& RaceDataDefinition) { if (RaceDataDefinition.Datas.Num() > 0) { UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Receiving RaceDataDefinition [%s]"), *RaceDataDefinition.Datas[0].Name); if (DataStorage != nullptr) { UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage Name %s"), *DataStorage->EventName); for (auto Contest : RaceDataDefinition.Datas) { DataStorage->AddContest(Contest); } } else { UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage is null")); } SaveDataStorage(); return; } UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("RaceDataDefinition is empty !!!")); } void UDTFluxCoreSubsystem::ProcessTeamList(const FDTFluxTeamListDefinition& TeamListDefinition) { UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received TeamList with %i Items"), TeamListDefinition.Participants.Num()); for (const auto& Participant : TeamListDefinition.Participants) { UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Add Participant %i in %i ContestId"), Participant.Bib, Participant.ContestId); DataStorage->AddParticipant(Participant, Participant.ContestId); } SaveDataStorage(); } void UDTFluxCoreSubsystem::ProcessContestRanking(const FDTFluxContestRankings& ContestRankings) { UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received ContestRankings with %i Items"), ContestRankings.Rankings.Num()); FDTFluxContestRankings NewContestRankings = ContestRankings; NewContestRankings.SetName(DataStorage->GetContestNameForId(ContestRankings.ContestId)); DataStorage->AddContestRanking(NewContestRankings); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRankings added for Contest %s"), *NewContestRankings.ContestName); if (bShouldKeepRankings) { SaveDataStorage(); } } void UDTFluxCoreSubsystem::ProcessStageRanking(const FDTFluxStageRankings& StageRankings) { UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received StageRankings with %i Items"), StageRankings.Rankings.Num()); DataStorage->UpdateOrCreateStageRanking(StageRankings); if (bShouldKeepRankings) { SaveDataStorage(); } } void UDTFluxCoreSubsystem::ProcessSplitRanking(const FDTFluxSplitRankings& SplitRankings) { UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received SplitRanking with %i Items"), SplitRankings.Rankings.Num()); DataStorage->UpdateOrCreateSplitRanking(SplitRankings); if (bShouldKeepRankings) { SaveDataStorage(); } } void UDTFluxCoreSubsystem::ProcessTeamStatusUpdate(const FDTFluxTeamStatusUpdate& NewParticipantStatus) { DataStorage->UpdateParticipantStatus(NewParticipantStatus); } void UDTFluxCoreSubsystem::ProcessTeamUpdate(const FDTFluxTeamListDefinition& TeamListDefinitiont) { for (const auto& Participant : TeamListDefinitiont.Participants) { DataStorage->UpdateParticipant(Participant); } } void UDTFluxCoreSubsystem::ProcessSplitSensor(const FDTFluxSplitSensorInfo& SplitSensorInfo) { FDTFluxContest Contest; FDTFluxStageKey StageKey(SplitSensorInfo.ContestId, SplitSensorInfo.StageId); FDTFluxStage Stage; DataStorage->GetStage(StageKey, Stage); FDTFluxParticipant Participant; DataStorage->GetParticipantByBib(SplitSensorInfo.Bib, Participant); DataStorage->GetContestById(SplitSensorInfo.ContestId, Contest); UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("%s|%s Split %i Sensor for Participant [Bib] %i [FullName] %s"), *Contest.Name, *Stage.Name, SplitSensorInfo.SplitId, SplitSensorInfo.Bib, *Participant.GetFormattedName()); } void UDTFluxCoreSubsystem::SendRequest(const FString& Message) { if (NetworkSubsystem) { NetworkSubsystem->SendMessage(Message); } } bool UDTFluxCoreSubsystem::GetContestRankings(const int ContestId, FDTFluxContestRankings& OutContestRankings) { if (DataStorage->ContestRankings.Contains(ContestId)) { OutContestRankings = DataStorage->ContestRankings[ContestId]; return true; } if (NetworkSubsystem) { TArray TackedContestIds = {ContestId}; TrackedRequestContestRankings(TackedContestIds); return false; } UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("NetworkSubsystem unavailable")); return false; } bool UDTFluxCoreSubsystem::GetStageRankings(const int ContestId, const int StageId, FDTFluxStageRankings& OutStageRankings) { return GetStageRankingsWithKey(FDTFluxStageKey(ContestId, StageId), OutStageRankings); } bool UDTFluxCoreSubsystem::GetSplitRankings(const int ContestId, const int StageId, const int SplitId, FDTFluxSplitRankings& OutSplitRankings) { return GetSplitRankingsWithKey(FDTFluxSplitKey(ContestId, StageId, SplitId), OutSplitRankings); } bool UDTFluxCoreSubsystem::GetStageRankingsWithKey(const FDTFluxStageKey StageKey, FDTFluxStageRankings& OutStageRankings, const bool bShouldUseCached) { //We Have the data if (DataStorage->StageRankings.Contains(StageKey) && bShouldUseCached) { OutStageRankings = DataStorage->StageRankings[StageKey]; return true; } else { if (NetworkSubsystem) { TArray TackedStageKeys = {StageKey}; TrackedRequestStageRankings(TackedStageKeys); OutStageRankings = FDTFluxStageRankings(); return false; } UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("NetworkSubsystem unavailable")) } return false; } bool UDTFluxCoreSubsystem::GetSplitRankingsWithKey(const FDTFluxSplitKey SplitKey, FDTFluxSplitRankings& OutSplitRankings, const bool bShouldUseCached) { //We Have the data if (DataStorage->SplitRankings.Contains(SplitKey) && bShouldUseCached) { OutSplitRankings = DataStorage->SplitRankings[SplitKey]; return true; } else { if (NetworkSubsystem) { TArray TackedSplitKey = {SplitKey}; TrackedRequestSplitRankings(TackedSplitKey); OutSplitRankings = FDTFluxSplitRankings(); return false; } UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("NetworkSubsystem unavailable")) return false; } } TArray UDTFluxCoreSubsystem::TrackedRequestContestRankings(const TArray ForContests, bool bEnableCache) { if (NetworkSubsystem) { TArray RequestIds; FOnDTFluxRequestSuccess OnSuccess = FOnDTFluxRequestSuccess::CreateLambda( [this](const FDTFluxTrackedRequest& Request) { UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("ContestRanking Request %s %s Success"), *Request.RequestId.ToString(), *UEnum::GetValueAsString(Request.RequestType)); if (Request.ParsedResponse.IsSet()) { ProcessTrackedResponse(*Request.ParsedResponse.GetValue()); } }); FOnDTFluxRequestError OnError = FOnDTFluxRequestError::CreateLambda( [this](const FDTFluxTrackedRequest& InReq, const FString& InError) { UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("ContestRanking Request [%s] Error %s"), *InReq.RequestId.ToString(), *InError); }); // if Contest is not ended for (auto ContestId : ForContests) { FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::ContestRanking, ContestId, -1, -1, OnSuccess, OnError, bEnableCache); RequestIds.Add(ContestRequest); } return RequestIds; } return TArray(); } TArray UDTFluxCoreSubsystem::TrackedRequestStageRankings(const TArray ForStages, bool bEnableCache) { if (NetworkSubsystem) { TArray RequestIds; FOnDTFluxRequestSuccess OnSuccess = FOnDTFluxRequestSuccess::CreateLambda( [this](const FDTFluxTrackedRequest& Request) { UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Stage Request %s %s Success"), *Request.RequestId.ToString(), *UEnum::GetValueAsString(Request.RequestType)); if (Request.ParsedResponse.IsSet()) { ProcessTrackedResponse(*Request.ParsedResponse.GetValue()); } }); FOnDTFluxRequestError OnError = FOnDTFluxRequestError::CreateLambda( [this](const FDTFluxTrackedRequest& InReq, const FString& InError) { UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("StageRanking Request [%s] Error %s"), *InReq.RequestId.ToString(), *InError); }); // if Contest is not ended for (auto StageKey : ForStages) { FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::StageRanking, StageKey.ContestId, StageKey.StageId, -1, OnSuccess, OnError, bEnableCache); RequestIds.Add(ContestRequest); } return RequestIds; } return TArray(); } TArray UDTFluxCoreSubsystem::TrackedRequestSplitRankings(const TArray ForSplits, bool bEnableCache) { if (NetworkSubsystem) { TArray RequestIds; FOnDTFluxRequestSuccess OnSuccess = FOnDTFluxRequestSuccess::CreateLambda( [this](const FDTFluxTrackedRequest& Request) { UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Stage Request %s %s Success"), *Request.RequestId.ToString(), *UEnum::GetValueAsString(Request.RequestType)); if (Request.ParsedResponse.IsSet()) { ProcessTrackedResponse(*Request.ParsedResponse.GetValue()); } }); FOnDTFluxRequestError OnError = FOnDTFluxRequestError::CreateLambda( [this](const FDTFluxTrackedRequest& InReq, const FString& InError) { UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("StageRanking Request [%s] Error %s"), *InReq.RequestId.ToString(), *InError); }); // if Contest is not ended for (auto SplitKey : ForSplits) { FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::SplitRanking, SplitKey.ContestId, SplitKey.StageId, SplitKey.SplitId, OnSuccess, OnError, bEnableCache); RequestIds.Add(ContestRequest); } return RequestIds; } return TArray(); } const FDTFluxParticipant UDTFluxCoreSubsystem::GetParticipant(int InBib) { if (DataStorage->Participants.Contains(InBib)) { return DataStorage->Participants[InBib]; } return FDTFluxParticipant(); } TArray UDTFluxCoreSubsystem::GetCurrentContestsId() { return GetContestsIdForTime(FDateTime::Now()); } TArray UDTFluxCoreSubsystem::GetCurrentContests() { return GetContestsForTime(FDateTime::Now()); } TArray UDTFluxCoreSubsystem::GetContestsIdForTime(const FDateTime Time) const { TArray Contests; for (const auto& Pair : DataStorage->Contests) { FDTFluxContest Contest = Pair.Value; int ContestId = Contest.ContestId; if (Contest.Date < Time && Contest.EndTime > Time) { Contests.Add(ContestId); } } return Contests; } bool UDTFluxCoreSubsystem::GetContestForId(const int Id, FDTFluxContest& OutContest) { for (auto KeyPair : DataStorage->Contests) { if (KeyPair.Value.ContestId == Id) { OutContest = KeyPair.Value; return true; } } UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Cannot find Contest for Id [%i]"), Id); return false; } TArray UDTFluxCoreSubsystem::GetContestsForTime(const FDateTime Time) { TArray Contests; for (const auto& Pair : DataStorage->Contests) { FDTFluxContest Contest = Pair.Value; int ContestId = Contest.ContestId; if (Contest.Date < Time && Contest.EndTime > Time) { Contests.Add(Contest); } } return Contests; } void UDTFluxCoreSubsystem::RequestRankingsForStages(TArray RequestedStages) const { } TArray UDTFluxCoreSubsystem::GetContests() { if (DataStorage) { TArray OutContests; DataStorage->Contests.GenerateValueArray(OutContests); return OutContests; } return TArray(); }