Compare commits
16 Commits
81bf37639b
...
feature/Ra
| Author | SHA1 | Date | |
|---|---|---|---|
| d419681172 | |||
| 03eb1132ef | |||
| 8f884f6224 | |||
| 9bb5e760f2 | |||
| 760a764816 | |||
| 43a7fb7400 | |||
| b63f2dd7b5 | |||
| 7e1ce2cdfa | |||
| a2be97cfe4 | |||
| e0edf5ab8d | |||
| 8387319bf8 | |||
| 27822229d0 | |||
| 801e946a89 | |||
| fa5493adcf | |||
| 2855fa1e98 | |||
| 11e695f379 |
@ -44,6 +44,11 @@
|
|||||||
"Name": "DTFluxUtilities",
|
"Name": "DTFluxUtilities",
|
||||||
"Type": "Runtime",
|
"Type": "Runtime",
|
||||||
"LoadingPhase": "Default"
|
"LoadingPhase": "Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "DTFluxAPIStatus",
|
||||||
|
"Type": "Editor",
|
||||||
|
"LoadingPhase": "Default"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Plugins": [
|
"Plugins": [
|
||||||
|
|||||||
9
Notes.md
Normal file
9
Notes.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DEV NOTES
|
||||||
|
|
||||||
|
# TO THINK
|
||||||
|
|
||||||
|
- [ ] Team-Update : il faut trouver une stratégie pour les cas ou Participant supprimé avec réaffectation de son Bib
|
||||||
|
- [ ] Team-Update : il faut trouver une stratégie pour les cas de réaffectation de Bib d'un Concurrent.
|
||||||
|
__Solutions Possibles__:
|
||||||
|
- Si le Bib de l'update n'existe pas alors, le participant à changer de Bib. ça signifie qu'il faut que les organisateur ne réaffecte pas un Bib déja existant.~~
|
||||||
|
- [ ] Extraire Stages et Splits des Contest Pour les Mettre à plat.
|
||||||
@ -1 +1 @@
|
|||||||
# DTFluxAPI Plugin for UNREAL ENGINE (version 5.4.2)
|
# DTFluxAPI Plugin for UNREAL ENGINE (version 5.6)
|
||||||
|
|||||||
41
Resources/DTFluxRaceResult16x16.svg
Normal file
41
Resources/DTFluxRaceResult16x16.svg
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="512"
|
||||||
|
height="512"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:export-filename="RaceResult.icone16x16.svg"
|
||||||
|
inkscape:export-xdpi="96"
|
||||||
|
inkscape:export-ydpi="96"
|
||||||
|
sodipodi:docname="RaceResult.icone16x16.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#505050"
|
||||||
|
inkscape:document-units="px" /><defs
|
||||||
|
id="defs1" /><g
|
||||||
|
inkscape:label="Calque 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(3.0517578e-5)"><rect
|
||||||
|
style="fill:#e22c2b;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect1"
|
||||||
|
width="512"
|
||||||
|
height="512"
|
||||||
|
x="-3.0517578e-05"
|
||||||
|
y="0" /><path
|
||||||
|
style="fill:none;stroke:#ffffff;stroke-width:15;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 154.64305,277.08769 -1.3605,-151.92206 146.48007,103.3977 v 184.12046 l 54.87334,-50.79185 V 199.53942 L 215.41187,92.967228 V 328.78654 Z"
|
||||||
|
id="path1" /></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
118
Resources/DTFluxServerStatusWhite.svg
Normal file
118
Resources/DTFluxServerStatusWhite.svg
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 32 32"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
sodipodi:docname="DTFluxServerStatusProject.svg"
|
||||||
|
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||||
|
inkscape:export-filename="..\..\..\0000WorkTechnique\0002dev\Yotta2025\Plugins\DTFluxApi\Resources\DTFluxServerStatusWhite.svg"
|
||||||
|
inkscape:export-xdpi="96"
|
||||||
|
inkscape:export-ydpi="96"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#505050"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:export-bgcolor="#ffffff00"
|
||||||
|
inkscape:zoom="12.473806"
|
||||||
|
inkscape:cx="2.4852078"
|
||||||
|
inkscape:cy="16.715027"
|
||||||
|
inkscape:window-width="3440"
|
||||||
|
inkscape:window-height="1351"
|
||||||
|
inkscape:window-x="-9"
|
||||||
|
inkscape:window-y="1"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Calque 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#f9f9f9;stroke-width:2.00998;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="M 9.9141729,1.5371294 H 2.0032533 V 30.462871 h 7.8293644"
|
||||||
|
id="path1" />
|
||||||
|
<rect
|
||||||
|
style="fill:none;stroke:#f9f9f9;stroke-width:1.80686;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
id="rect2"
|
||||||
|
width="19.193144"
|
||||||
|
height="4.2450275"
|
||||||
|
x="6.4034286"
|
||||||
|
y="3.8339815" />
|
||||||
|
<rect
|
||||||
|
style="fill:none;stroke:#f9f9f9;stroke-width:1.74838;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
id="rect3"
|
||||||
|
width="19.251621"
|
||||||
|
height="3.9626296"
|
||||||
|
x="6.483469"
|
||||||
|
y="24.212114" />
|
||||||
|
<ellipse
|
||||||
|
style="fill:none;stroke:#f9f9f9;stroke-width:2.03983;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
id="path3"
|
||||||
|
cx="16.10928"
|
||||||
|
cy="16.480982"
|
||||||
|
rx="4.8213382"
|
||||||
|
ry="4.8742285" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#f9f9f9;stroke-width:0.744524;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 15.911604,16.712764 2.263593,-1.701009"
|
||||||
|
id="path4" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#f9f9f9;stroke-width:1.2784;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 16.417491,11.616727 V 9.7754388"
|
||||||
|
id="path5" />
|
||||||
|
<rect
|
||||||
|
style="display:none;fill:none;stroke:#f9f9f9;stroke-width:2.034;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect5"
|
||||||
|
width="18.966003"
|
||||||
|
height="10.100974"
|
||||||
|
x="6.5169978"
|
||||||
|
y="11.082275" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#f9f9f9;stroke-width:2.00998;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 22.304385,30.58532 h 7.91092 V 1.6595782 h -7.829364"
|
||||||
|
id="path6" />
|
||||||
|
<ellipse
|
||||||
|
style="fill:#f9f9f9;fill-opacity:1;stroke:none;stroke-width:7.96575;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path7"
|
||||||
|
cx="8.8708906"
|
||||||
|
cy="5.9564953"
|
||||||
|
rx="0.48769841"
|
||||||
|
ry="0.4282518" />
|
||||||
|
<ellipse
|
||||||
|
style="display:none;fill:#000000;fill-opacity:1;stroke:#f9f9f9;stroke-width:7.96575;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="ellipse7"
|
||||||
|
cx="8.8708906"
|
||||||
|
cy="13.527604"
|
||||||
|
rx="0.48769841"
|
||||||
|
ry="0.4282518" />
|
||||||
|
<ellipse
|
||||||
|
style="display:none;fill:#000000;fill-opacity:1;stroke:#f9f9f9;stroke-width:7.96575;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="ellipse8"
|
||||||
|
cx="8.8708906"
|
||||||
|
cy="18.456623"
|
||||||
|
rx="0.48769841"
|
||||||
|
ry="0.4282518" />
|
||||||
|
<ellipse
|
||||||
|
style="display:inline;fill:#f9f9f9;fill-opacity:1;stroke:none;stroke-width:7.96575;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="ellipse9"
|
||||||
|
cx="8.8708906"
|
||||||
|
cy="26.246185"
|
||||||
|
rx="0.48769841"
|
||||||
|
ry="0.4282518" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.4 KiB |
37
Source/DTFluxAPIStatus/DTFluxAPIStatus.Build.cs
Normal file
37
Source/DTFluxAPIStatus/DTFluxAPIStatus.Build.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using UnrealBuildTool;
|
||||||
|
|
||||||
|
public class DTFluxAPIStatus : ModuleRules
|
||||||
|
{
|
||||||
|
public DTFluxAPIStatus(ReadOnlyTargetRules Target) : base(Target)
|
||||||
|
{
|
||||||
|
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
|
||||||
|
PublicDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"Core", "DTFluxCoreSubsystem",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
PrivateDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"CoreUObject",
|
||||||
|
"Engine",
|
||||||
|
"Slate",
|
||||||
|
"SlateCore",
|
||||||
|
"Projects",
|
||||||
|
"DTFluxNetwork",
|
||||||
|
"DTFluxProjectSettings",
|
||||||
|
"DTFluxCore",
|
||||||
|
"EditorStyle",
|
||||||
|
"ToolWidgets", // Nécessaire pour FSlimHorizontalToolBarBuilder
|
||||||
|
"UnrealEd",
|
||||||
|
"Settings",
|
||||||
|
"DTFluxCoreSubsystem",
|
||||||
|
"InputCore",
|
||||||
|
"OutputLog",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
111
Source/DTFluxAPIStatus/Private/DTFluxAPIStatusModule.cpp
Normal file
111
Source/DTFluxAPIStatus/Private/DTFluxAPIStatusModule.cpp
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
#include "DTFluxAPIStatusModule.h"
|
||||||
|
|
||||||
|
#include "LevelEditor.h"
|
||||||
|
#include "widgets/DTFluxStatusWidget.h"
|
||||||
|
#include "widgets/styles/DTFluxStatusStyle.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "FDTFluxAPIStatusModule"
|
||||||
|
|
||||||
|
DEFINE_LOG_CATEGORY(logDTFluxStatus);
|
||||||
|
|
||||||
|
FName DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::StatusTabId = "DTFluxStatusTab";
|
||||||
|
FText DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::StatusTabDisplayName = FText::FromString(TEXT("DTFlux Status"));
|
||||||
|
|
||||||
|
|
||||||
|
void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::StartupModule()
|
||||||
|
{
|
||||||
|
InitMenuExtension();
|
||||||
|
RegisterStatusTab();
|
||||||
|
FDTFluxStatusStyle::RegisterStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma region MenuExtension
|
||||||
|
|
||||||
|
|
||||||
|
void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::InitMenuExtension()
|
||||||
|
{
|
||||||
|
FLevelEditorModule& LevelEditorModule =
|
||||||
|
FModuleManager::LoadModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
|
||||||
|
// FDTFluxAPIModule& DTFluxApi =
|
||||||
|
// FModuleManager::LoadModuleChecked<FDTFluxAPIModule>(TEXT("DTFluxAPI"));
|
||||||
|
const TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender());
|
||||||
|
|
||||||
|
MenuExtender->AddMenuBarExtension(
|
||||||
|
"Help",
|
||||||
|
EExtensionHook::Before,
|
||||||
|
nullptr,
|
||||||
|
FMenuBarExtensionDelegate::CreateRaw(this, &FDTFluxAPIStatusModule::AddMenu)
|
||||||
|
);
|
||||||
|
LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::AddMenu(FMenuBarBuilder& MenuBarBuilder)
|
||||||
|
{
|
||||||
|
MenuBarBuilder.AddPullDownMenu(
|
||||||
|
FText::FromString("DTFlux"),
|
||||||
|
FText::FromString("DTFlux API Tools"),
|
||||||
|
FNewMenuDelegate::CreateRaw(this, &FDTFluxAPIStatusModule::FillMenu)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::FillMenu(FMenuBuilder& MenuBuilder)
|
||||||
|
{
|
||||||
|
MenuBuilder.BeginSection(NAME_None, FText::FromString("DTFlux API"));
|
||||||
|
MenuBuilder.AddMenuEntry(
|
||||||
|
FText::FromString("Status"),
|
||||||
|
FText::FromString("Launch DTFlux Status"),
|
||||||
|
FSlateIcon(FDTFluxStatusStyle::GetStyleSetName(), "LevelEditor.Tab.Icon"),
|
||||||
|
FExecuteAction::CreateRaw(this, &FDTFluxAPIStatusModule::OnButtonClicked)
|
||||||
|
);
|
||||||
|
MenuBuilder.EndSection();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::OnButtonClicked()
|
||||||
|
{
|
||||||
|
FGlobalTabmanager::Get()->TryInvokeTab(StatusTabId);
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("Status Tab Launched"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion EditorTab
|
||||||
|
|
||||||
|
#pragma region
|
||||||
|
void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::RegisterStatusTab()
|
||||||
|
{
|
||||||
|
FTabSpawnerEntry& SpawnerEntry =
|
||||||
|
FGlobalTabmanager::Get()->RegisterNomadTabSpawner(
|
||||||
|
StatusTabId,
|
||||||
|
FOnSpawnTab::CreateRaw(this, &FDTFluxAPIStatusModule::OnSpawnTab)
|
||||||
|
)
|
||||||
|
.SetDisplayName(StatusTabDisplayName)
|
||||||
|
.SetTooltipText(FText::FromString(TEXT("Status of DTFlux API external connections")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedRef<SDockTab> DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::OnSpawnTab(const FSpawnTabArgs& SpawnTabArgs)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
return
|
||||||
|
SNew(
|
||||||
|
SDockTab
|
||||||
|
)
|
||||||
|
.TabRole(ETabRole::NomadTab)
|
||||||
|
// .ShouldAutosize(true)
|
||||||
|
[
|
||||||
|
SNew(SDTFluxStatusWidget)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::ShutdownModule()
|
||||||
|
{
|
||||||
|
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(StatusTabId);
|
||||||
|
FDTFluxStatusStyle::UnregisterStyle();
|
||||||
|
|
||||||
|
}
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|
||||||
|
IMPLEMENT_MODULE(FDTFluxAPIStatusModule, DTFluxAPIStatus)
|
||||||
448
Source/DTFluxAPIStatus/Private/DTFluxStatusWidget.cpp
Normal file
448
Source/DTFluxAPIStatus/Private/DTFluxStatusWidget.cpp
Normal file
@ -0,0 +1,448 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "widgets/DTFluxStatusWidget.h"
|
||||||
|
|
||||||
|
#include "SlateOptMacros.h"
|
||||||
|
#include "DTFluxAPIStatusModule.h"
|
||||||
|
#include "DTFluxCoreSubsystem.h"
|
||||||
|
#include "ISettingsModule.h"
|
||||||
|
#include "MovieSceneSequenceID.h"
|
||||||
|
#include "OutputLogCreationParams.h"
|
||||||
|
#include "OutputLogModule.h"
|
||||||
|
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||||
|
#include "Subsystems/DTFluxNetworkSubsystem.h"
|
||||||
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
||||||
|
|
||||||
|
|
||||||
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||||
|
|
||||||
|
void SDTFluxStatusWidget::OnOpenSettingsClicked()
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxStatus, Warning, TEXT("Settings Clicked !!!!"));
|
||||||
|
ISettingsModule& SettingsModule = FModuleManager::LoadModuleChecked<ISettingsModule>("Settings");
|
||||||
|
SettingsModule.ShowViewer("Project", "DTFluxProjectSettings", "DTFluxNetworkSettings");
|
||||||
|
}
|
||||||
|
|
||||||
|
FReply SDTFluxStatusWidget::OnRaceDatasClicked()
|
||||||
|
{
|
||||||
|
DTFluxNetwork->SendRequest(EDTFluxRequestType::RaceData);
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
FReply SDTFluxStatusWidget::OnTeamListClicked()
|
||||||
|
{
|
||||||
|
DTFluxNetwork->SendRequest(EDTFluxRequestType::TeamList);
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxStatusWidget::Construct(const FArguments& InArgs)
|
||||||
|
{
|
||||||
|
DTFluxNetwork =
|
||||||
|
GEngine->GetEngineSubsystem<UDTFluxNetworkSubsystem>();
|
||||||
|
DTFluxCore =
|
||||||
|
GEngine->GetEngineSubsystem<UDTFluxCoreSubsystem>();
|
||||||
|
ConnectionActionButtonText.Set(
|
||||||
|
DTFluxNetwork->WsStatus != EDTFluxConnectionStatus::Connected
|
||||||
|
? FText::FromString("Connect")
|
||||||
|
: FText::FromString("Disconnect")
|
||||||
|
);
|
||||||
|
FOutputLogModule& OutputLogModule = FModuleManager::LoadModuleChecked<FOutputLogModule>("OutputLog");
|
||||||
|
const FOutputLogCreationParams Params;
|
||||||
|
bCanSupportFocus = true;
|
||||||
|
PopulateComboBoxItems();
|
||||||
|
|
||||||
|
FSlimHorizontalToolBarBuilder ToolBarBuilder(
|
||||||
|
nullptr,
|
||||||
|
FMultiBoxCustomization::None,
|
||||||
|
nullptr,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
ToolBarBuilder.BeginSection("Settings");
|
||||||
|
{
|
||||||
|
ToolBarBuilder.AddToolBarButton(
|
||||||
|
FUIAction(FExecuteAction::CreateSP(this, &SDTFluxStatusWidget::OnOpenSettingsClicked)),
|
||||||
|
NAME_None,
|
||||||
|
INVTEXT("DTFlux Settings"),
|
||||||
|
INVTEXT("Ouvrir les paramètres DTFlux"),
|
||||||
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Settings")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ToolBarBuilder.EndSection();
|
||||||
|
|
||||||
|
|
||||||
|
FSlateFontInfo TitleTextFont = FCoreStyle::Get().GetFontStyle(FName("EmbossedText"));
|
||||||
|
TitleTextFont.Size = 15;
|
||||||
|
ChildSlot
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
||||||
|
[
|
||||||
|
SNew(SVerticalBox)
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
[
|
||||||
|
#pragma region ToolBarSection
|
||||||
|
SNew(SBox)
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.FillWidth(2.0)
|
||||||
|
[
|
||||||
|
SNew(SSpacer)
|
||||||
|
]
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.AutoWidth()
|
||||||
|
.FillWidth(1.0)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
.HAlign(HAlign_Right)
|
||||||
|
[
|
||||||
|
ToolBarBuilder.MakeWidget()
|
||||||
|
]
|
||||||
|
]
|
||||||
|
#pragma endregion
|
||||||
|
]
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
[
|
||||||
|
#pragma region WebsocketStatusSection
|
||||||
|
SNew(SBorder)
|
||||||
|
.Padding(6.0f)
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
.HAlign(HAlign_Center)
|
||||||
|
.MaxWidth(175.0)
|
||||||
|
.MinWidth(150.0)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(FText::FromString(TEXT("Websocket connection :")))
|
||||||
|
.Justification(ETextJustify::Left)
|
||||||
|
]
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
.MinWidth(50.0)
|
||||||
|
.MaxWidth(100.0)
|
||||||
|
[
|
||||||
|
SAssignNew(WsStatusText, STextBlock)
|
||||||
|
.Text(this, &SDTFluxStatusWidget::GetWebSocketStatusText)
|
||||||
|
.Justification(ETextJustify::Left)
|
||||||
|
.ColorAndOpacity(this, &SDTFluxStatusWidget::GetWebSocketStatusColor)
|
||||||
|
]
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.MaxWidth(100.0)
|
||||||
|
.MinWidth(30.0)
|
||||||
|
[
|
||||||
|
SAssignNew(ConnectionActionButton, SButton)
|
||||||
|
.Text(this, &SDTFluxStatusWidget::GetWebConnectActionButtonText)
|
||||||
|
.ForegroundColor_Raw(this, &SDTFluxStatusWidget::GetWebConnectActionButtonColor)
|
||||||
|
.OnClicked(this, &SDTFluxStatusWidget::OnConnectionActionButtonClicked)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
.HAlign(HAlign_Center)
|
||||||
|
.ContentPadding(1.5f)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
#pragma endregion
|
||||||
|
]
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.VAlign(VAlign_Fill)
|
||||||
|
.HAlign(HAlign_Fill)
|
||||||
|
.AutoHeight()
|
||||||
|
[
|
||||||
|
#pragma region DataModelControlSection
|
||||||
|
SNew(SBorder)
|
||||||
|
.Padding(6.0f)
|
||||||
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
[
|
||||||
|
SNew(SButton)
|
||||||
|
.Text(FText::FromString("Get RaceDatas"))
|
||||||
|
.OnClicked(this, &SDTFluxStatusWidget::OnRaceDatasClicked)
|
||||||
|
]
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
[
|
||||||
|
SNew(SButton)
|
||||||
|
.Text(FText::FromString("Get TeamList"))
|
||||||
|
.OnClicked(this, &SDTFluxStatusWidget::OnTeamListClicked)
|
||||||
|
]
|
||||||
|
|
||||||
|
]
|
||||||
|
#pragma endregion
|
||||||
|
]
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.VAlign(VAlign_Fill)
|
||||||
|
.HAlign(HAlign_Fill)
|
||||||
|
.AutoHeight()
|
||||||
|
[
|
||||||
|
#pragma region RankingSection
|
||||||
|
SNew(SBorder)
|
||||||
|
.Padding(6.0f)
|
||||||
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.Padding(6.0f)
|
||||||
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
.HAlign(HAlign_Left)
|
||||||
|
.AutoWidth()
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(FText::FromString(TEXT("Contest Rankings:")))
|
||||||
|
.Margin(FMargin(0.0f, 0.0f, 8.0f, 0.0f))
|
||||||
|
]
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
.FillWidth(1.0f)
|
||||||
|
.MaxWidth(200.0f)
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SAssignNew(ContestComboBox, SComboBox<TSharedPtr<FComboBoxItem>>)
|
||||||
|
.OptionsSource(&ContestComboBoxItems)
|
||||||
|
.OnGenerateWidget(this, &SDTFluxStatusWidget::OnGeneRankingComboWidget)
|
||||||
|
.OnSelectionChanged(this, &SDTFluxStatusWidget::OnComboContestRankingChanged)
|
||||||
|
.Content()
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(this, &SDTFluxStatusWidget::GetCurrContestComboBoxValue)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
.AutoWidth()
|
||||||
|
.Padding(FMargin(8.0f, 0.0f, 0.0f, 0.0f))
|
||||||
|
[
|
||||||
|
SNew(SButton)
|
||||||
|
.Text(FText::FromString(TEXT("RequestRanking")))
|
||||||
|
.OnClicked(this, &SDTFluxStatusWidget::OnRankingButtonClicked)
|
||||||
|
.ContentPadding(FMargin(12.0f, 4.0f))
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
.HAlign(HAlign_Center)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
]
|
||||||
|
]
|
||||||
|
#pragma endregion
|
||||||
|
]
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.FillHeight(1.0f)
|
||||||
|
.Padding(3.0f)
|
||||||
|
[
|
||||||
|
OutputLogModule.MakeOutputLogWidget(Params)
|
||||||
|
]
|
||||||
|
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
FText SDTFluxStatusWidget::GetWebSocketStatusText() const
|
||||||
|
{
|
||||||
|
FString Status =
|
||||||
|
UEnum::GetDisplayValueAsText(DTFluxNetwork->WsStatus).ToString();
|
||||||
|
return
|
||||||
|
FText::FromString(Status);
|
||||||
|
// FText::FromString("Unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
FText SDTFluxStatusWidget::GetWebConnectActionButtonText() const
|
||||||
|
{
|
||||||
|
switch (DTFluxNetwork->WsStatus)
|
||||||
|
{
|
||||||
|
case EDTFluxConnectionStatus::Connected:
|
||||||
|
return FText::FromString("Disconnect");
|
||||||
|
default:
|
||||||
|
return FText::FromString("Connect");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FReply SDTFluxStatusWidget::OnConnectionActionButtonClicked()
|
||||||
|
{
|
||||||
|
if (DTFluxNetwork)
|
||||||
|
{
|
||||||
|
switch (DTFluxNetwork->WsStatus)
|
||||||
|
{
|
||||||
|
case EDTFluxConnectionStatus::Connected:
|
||||||
|
DTFluxNetwork->Reconnect();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DTFluxNetwork->Connect();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
FSlateColor SDTFluxStatusWidget::GetWebSocketStatusColor() const
|
||||||
|
{
|
||||||
|
FColor Color;
|
||||||
|
switch (DTFluxNetwork->WsStatus)
|
||||||
|
{
|
||||||
|
case EDTFluxConnectionStatus::Unset:
|
||||||
|
Color = FColor::Orange;
|
||||||
|
break;
|
||||||
|
case EDTFluxConnectionStatus::Connected:
|
||||||
|
Color = FColor::Green;
|
||||||
|
break;
|
||||||
|
case EDTFluxConnectionStatus::NotConnected:
|
||||||
|
Color = FColor::Orange;
|
||||||
|
break;
|
||||||
|
case EDTFluxConnectionStatus::Closed:
|
||||||
|
Color = FColor::Magenta;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Color = FColor::Red;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return FSlateColor(Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
FSlateColor SDTFluxStatusWidget::GetWebConnectActionButtonColor() const
|
||||||
|
{
|
||||||
|
FColor Color = FColor::Green;
|
||||||
|
switch (DTFluxNetwork->WsStatus)
|
||||||
|
{
|
||||||
|
case EDTFluxConnectionStatus::Connected:
|
||||||
|
Color = FColor::Red;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Color = FColor::Green;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return FSlateColor(Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SDTFluxStatusWidget::PopulateComboBoxItems()
|
||||||
|
{
|
||||||
|
ContestComboBoxItems.Empty();
|
||||||
|
|
||||||
|
if (DTFluxNetwork)
|
||||||
|
{
|
||||||
|
FString Separator = " | ";
|
||||||
|
FString RootSeparator = " -> ";
|
||||||
|
TArray<FDTFluxContest> DataFromSubsystem = DTFluxCore->GetContests();
|
||||||
|
|
||||||
|
for (const auto& Contest : DataFromSubsystem)
|
||||||
|
{
|
||||||
|
ContestComboBoxItems.Add(
|
||||||
|
FComboBoxItem::CreateItem(EComboBoxItemType::Contest, Contest.Name + RootSeparator, Contest.ContestId));
|
||||||
|
|
||||||
|
|
||||||
|
for (const auto& Stage : Contest.Stages)
|
||||||
|
{
|
||||||
|
int StageId = Stage.StageId;
|
||||||
|
FString StageDisplayName = Contest.Name + Separator + Stage.Name;
|
||||||
|
ContestComboBoxItems.Add(FComboBoxItem::CreateItem(EComboBoxItemType::Stage,
|
||||||
|
StageDisplayName + RootSeparator, Contest.ContestId,
|
||||||
|
StageId));
|
||||||
|
for (const auto& Split : Contest.Splits)
|
||||||
|
{
|
||||||
|
ContestComboBoxItems.Add(FComboBoxItem::CreateItem(EComboBoxItemType::Split,
|
||||||
|
StageDisplayName + Separator + Split.Name,
|
||||||
|
Contest.ContestId, StageId, Split.SplitId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ContestComboBox.IsValid())
|
||||||
|
{
|
||||||
|
ContestComboBox->RefreshOptions();
|
||||||
|
if (ContestComboBoxItems.Num() > 0)
|
||||||
|
{
|
||||||
|
ContestComboBox->SetSelectedItem(ContestComboBoxItems[0]);
|
||||||
|
SelectedContestComboBoxItem = ContestComboBoxItems[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedRef<SWidget> SDTFluxStatusWidget::OnGeneRankingComboWidget(TSharedPtr<FComboBoxItem> InItem)
|
||||||
|
{
|
||||||
|
return SNew(STextBlock)
|
||||||
|
.Text(FText::FromString(InItem->DisplayText))
|
||||||
|
.ColorAndOpacity(GetComboItemRankingColor(InItem))
|
||||||
|
.Margin(FMargin(2.0f, 1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxStatusWidget::OnComboContestRankingChanged(TSharedPtr<FComboBoxItem> NewSelection,
|
||||||
|
ESelectInfo::Type SelectInfo)
|
||||||
|
{
|
||||||
|
SelectedContestComboBoxItem = NewSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
FText SDTFluxStatusWidget::GetCurrContestComboBoxValue() const
|
||||||
|
{
|
||||||
|
if (SelectedContestComboBoxItem.IsValid())
|
||||||
|
{
|
||||||
|
return FText::FromString(SelectedContestComboBoxItem->DisplayText);
|
||||||
|
}
|
||||||
|
return FText::FromString("None");
|
||||||
|
}
|
||||||
|
|
||||||
|
FSlateColor SDTFluxStatusWidget::GetComboItemRankingColor(const TSharedPtr<FComboBoxItem> Item)
|
||||||
|
{
|
||||||
|
switch (Item->Type)
|
||||||
|
{
|
||||||
|
case EComboBoxItemType::Contest:
|
||||||
|
return FSlateColor(FLinearColor(0.2f, 0.6f, 1.0f));
|
||||||
|
case EComboBoxItemType::Stage:
|
||||||
|
return FSlateColor(FLinearColor(0.2f, 0.8f, 0.2f));
|
||||||
|
case EComboBoxItemType::Split:
|
||||||
|
return FSlateColor(FLinearColor(1.0f, 0.8f, 0.2f));
|
||||||
|
case EComboBoxItemType::None:
|
||||||
|
return FSlateColor(FLinearColor(0.6f, 0.28f, 0.28f));
|
||||||
|
default:
|
||||||
|
return FSlateColor(FLinearColor::White);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FReply SDTFluxStatusWidget::OnRankingButtonClicked() const
|
||||||
|
{
|
||||||
|
if (DTFluxNetwork)
|
||||||
|
{
|
||||||
|
// Exemple d'envoi de requête basée sur la sélection
|
||||||
|
int ForContest = SelectedContestComboBoxItem.IsValid() ? SelectedContestComboBoxItem->ContestId : -1;
|
||||||
|
int ForStage = SelectedContestComboBoxItem.IsValid() ? SelectedContestComboBoxItem->StageId : -1;
|
||||||
|
int ForSplit = SelectedContestComboBoxItem.IsValid() ? SelectedContestComboBoxItem->SplitId : -1;
|
||||||
|
|
||||||
|
EDTFluxApiDataType RequestType = EDTFluxApiDataType::None;
|
||||||
|
if (ForContest == -1)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxStatus, Error, TEXT("Contest not selected !!!!"));
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
if (ForStage == -1)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxStatus, Warning, TEXT("Stage not selected !!!! Requesting contest Ranking"));
|
||||||
|
RequestType = EDTFluxApiDataType::ContestRanking;
|
||||||
|
DTFluxNetwork->SendRequest(RequestType, ForContest);
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
RequestType = ForSplit == -1 ? EDTFluxApiDataType::StageRanking : EDTFluxApiDataType::SplitRanking;
|
||||||
|
UE_LOG(logDTFluxStatus, Warning, TEXT("Requesting %s Ranking"), *UEnum::GetValueAsString(RequestType));
|
||||||
|
DTFluxNetwork->SendRequest(RequestType, ForContest, ForStage, ForSplit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||||
46
Source/DTFluxAPIStatus/Private/styles/DTFluxStatusStyle.cpp
Normal file
46
Source/DTFluxAPIStatus/Private/styles/DTFluxStatusStyle.cpp
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "widgets/styles/DTFluxStatusStyle.h"
|
||||||
|
|
||||||
|
#include "Interfaces/IPluginManager.h"
|
||||||
|
#include "Styling/SlateStyleRegistry.h"
|
||||||
|
#include "Styling/SlateStyleMacros.h"
|
||||||
|
|
||||||
|
#define RootToContentDir Style->RootToContentDir
|
||||||
|
|
||||||
|
TSharedPtr<ISlateStyle> FDTFluxStatusStyle::StyleSet = nullptr;
|
||||||
|
|
||||||
|
void FDTFluxStatusStyle::RegisterStyle()
|
||||||
|
{
|
||||||
|
if(StyleSet.IsValid()) return;
|
||||||
|
|
||||||
|
StyleSet = Create();
|
||||||
|
FSlateStyleRegistry::RegisterSlateStyle(*StyleSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxStatusStyle::UnregisterStyle()
|
||||||
|
{
|
||||||
|
if(StyleSet.IsValid())
|
||||||
|
{
|
||||||
|
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet);
|
||||||
|
ensure(StyleSet.IsUnique());
|
||||||
|
StyleSet.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxStatusStyle::ReloadTextures()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<ISlateStyle> FDTFluxStatusStyle::Create()
|
||||||
|
{
|
||||||
|
TSharedPtr<FSlateStyleSet> Style = MakeShareable(new FSlateStyleSet("DTFluxAPIStatusStyle"));
|
||||||
|
Style->SetContentRoot(IPluginManager::Get().FindPlugin("DTFluxAPI")->GetBaseDir()/TEXT("Resources"));
|
||||||
|
|
||||||
|
Style->Set("LevelEditor.Tab.Icon", new IMAGE_BRUSH_SVG("DTFluxServerStatusWhite", FVector2d(16)) );
|
||||||
|
return Style;
|
||||||
|
}
|
||||||
|
|
||||||
31
Source/DTFluxAPIStatus/Public/DTFluxAPIStatusModule.h
Normal file
31
Source/DTFluxAPIStatus/Public/DTFluxAPIStatusModule.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
|
DTFLUXAPISTATUS_API DECLARE_LOG_CATEGORY_EXTERN(logDTFluxStatus, All, All);
|
||||||
|
|
||||||
|
class DTFLUXAPISTATUS_API FDTFluxAPIStatusModule : public IModuleInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void StartupModule() override;
|
||||||
|
virtual void ShutdownModule() override;
|
||||||
|
|
||||||
|
#pragma region MenuExtention
|
||||||
|
void InitMenuExtension();
|
||||||
|
void AddMenu(FMenuBarBuilder& MenuBarBuilder);
|
||||||
|
void FillMenu(FMenuBuilder& MenuBuilder);
|
||||||
|
void OnButtonClicked();
|
||||||
|
// void OnWsEvent(TEnumAsByte<EDTFluxWsStatus> WsResponseEvent) const;
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
#pragma region EditorTab
|
||||||
|
void RegisterStatusTab();
|
||||||
|
TSharedRef<SDockTab> OnSpawnTab(const FSpawnTabArgs& SpawnTabArgs);
|
||||||
|
private:
|
||||||
|
static FName StatusTabId;
|
||||||
|
static FText StatusTabDisplayName;
|
||||||
|
|
||||||
|
TSharedPtr<class SDTFluxStatusWidget> StatusWidget;
|
||||||
|
#pragma endregion
|
||||||
|
};
|
||||||
92
Source/DTFluxAPIStatus/Public/widgets/DTFluxStatusWidget.h
Normal file
92
Source/DTFluxAPIStatus/Public/widgets/DTFluxStatusWidget.h
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Widgets/SCompoundWidget.h"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class UDTFluxNetworkSubsystem;
|
||||||
|
class UDTFluxCoreSubsystem;
|
||||||
|
class SSuperListView;
|
||||||
|
|
||||||
|
|
||||||
|
class DTFLUXAPISTATUS_API SDTFluxStatusWidget : public SCompoundWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SLATE_BEGIN_ARGS(SDTFluxStatusWidget)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SLATE_END_ARGS()
|
||||||
|
|
||||||
|
void OnOpenSettingsClicked();
|
||||||
|
FReply OnRaceDatasClicked();
|
||||||
|
FReply OnTeamListClicked();
|
||||||
|
/** Constructs this widget with InArgs */
|
||||||
|
void Construct(const FArguments& InArgs);
|
||||||
|
TAttribute<FText> ConnectionActionButtonText;
|
||||||
|
FReply OnConnectionActionButtonClicked();
|
||||||
|
|
||||||
|
enum class EComboBoxItemType
|
||||||
|
{
|
||||||
|
Contest,
|
||||||
|
Stage,
|
||||||
|
Split,
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FComboBoxItem
|
||||||
|
{
|
||||||
|
FString DisplayText;
|
||||||
|
int ContestId = -1;
|
||||||
|
int StageId = -1;
|
||||||
|
int SplitId = -1;
|
||||||
|
EComboBoxItemType Type = EComboBoxItemType::None;
|
||||||
|
|
||||||
|
|
||||||
|
FComboBoxItem(const EComboBoxItemType InType, const FString& InDisplayText, const int InContestId,
|
||||||
|
const int InStageId = -1, const int InSplitId = -1)
|
||||||
|
: DisplayText(InDisplayText), ContestId(InContestId), StageId(InStageId), SplitId(InSplitId), Type(InType)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static TSharedPtr<FComboBoxItem> CreateItem(const EComboBoxItemType& InType, const FString& InDisplayText,
|
||||||
|
const int InContestId, const int InStageId = -1,
|
||||||
|
const int InSplitId = -1)
|
||||||
|
{
|
||||||
|
TSharedPtr<FComboBoxItem> Item = MakeShareable(
|
||||||
|
new FComboBoxItem(InType, InDisplayText, InContestId, InStageId, InSplitId));
|
||||||
|
return Item;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
UDTFluxNetworkSubsystem* DTFluxNetwork = nullptr;
|
||||||
|
UDTFluxCoreSubsystem* DTFluxCore = nullptr;
|
||||||
|
// // TODO make a struct
|
||||||
|
FText GetWebSocketStatusText() const;
|
||||||
|
FText GetWebConnectActionButtonText() const;
|
||||||
|
FSlateColor GetWebSocketStatusColor() const;
|
||||||
|
FSlateColor GetWebConnectActionButtonColor() const;
|
||||||
|
TSharedPtr<STextBlock> WsStatusText;
|
||||||
|
TSharedPtr<SButton> ConnectionActionButton;
|
||||||
|
|
||||||
|
TSharedPtr<SComboBox<TSharedPtr<FComboBoxItem>>> ContestComboBox;
|
||||||
|
TArray<TSharedPtr<FComboBoxItem>> ContestComboBoxItems;
|
||||||
|
TSharedPtr<FComboBoxItem> SelectedContestComboBoxItem;
|
||||||
|
|
||||||
|
|
||||||
|
// Méthodes pour le ComboBox
|
||||||
|
void PopulateComboBoxItems();
|
||||||
|
TSharedRef<SWidget> OnGeneRankingComboWidget(TSharedPtr<FComboBoxItem> InItem);
|
||||||
|
void OnComboContestRankingChanged(TSharedPtr<FComboBoxItem> NewSelection, ESelectInfo::Type SelectInfo);
|
||||||
|
FText GetCurrContestComboBoxValue() const;
|
||||||
|
|
||||||
|
static FSlateColor GetComboItemRankingColor(const TSharedPtr<FComboBoxItem> Item);
|
||||||
|
|
||||||
|
FReply OnRankingButtonClicked() const;
|
||||||
|
};
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Styling/ISlateStyle.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class DTFLUXAPISTATUS_API FDTFluxStatusStyle
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void RegisterStyle();
|
||||||
|
static void UnregisterStyle();
|
||||||
|
|
||||||
|
static void ReloadTextures();
|
||||||
|
|
||||||
|
static const ISlateStyle& Get()
|
||||||
|
{
|
||||||
|
return *StyleSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const FName& GetStyleSetName()
|
||||||
|
{
|
||||||
|
return StyleSet->GetStyleSetName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static TSharedPtr<ISlateStyle> Create();
|
||||||
|
static TSharedPtr<ISlateStyle> StyleSet;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ public class DTFluxAssetsEditor : ModuleRules
|
|||||||
PublicDependencyModuleNames.AddRange(
|
PublicDependencyModuleNames.AddRange(
|
||||||
new string[]
|
new string[]
|
||||||
{
|
{
|
||||||
"Core",
|
"Core"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -18,11 +18,20 @@ public class DTFluxAssetsEditor : ModuleRules
|
|||||||
{
|
{
|
||||||
"CoreUObject",
|
"CoreUObject",
|
||||||
"Engine",
|
"Engine",
|
||||||
|
"SlateCore",
|
||||||
"Slate",
|
"Slate",
|
||||||
"AssetTools",
|
"AssetTools",
|
||||||
"SlateCore",
|
|
||||||
"UnrealEd",
|
"UnrealEd",
|
||||||
"DTFluxCore",
|
"DTFluxCore",
|
||||||
|
"ToolMenus",
|
||||||
|
"EditorWidgets",
|
||||||
|
"EditorStyle",
|
||||||
|
"PropertyEditor",
|
||||||
|
"SharedSettingsWidgets",
|
||||||
|
"PropertyEditor",
|
||||||
|
"DesktopWidgets",
|
||||||
|
"ApplicationCore",
|
||||||
|
"InputCore"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,17 +3,21 @@
|
|||||||
#include "DTFluxAssetModelTypeActions.h"
|
#include "DTFluxAssetModelTypeActions.h"
|
||||||
#include "IAssetTools.h"
|
#include "IAssetTools.h"
|
||||||
#include "AssetToolsModule.h"
|
#include "AssetToolsModule.h"
|
||||||
|
#include "DTFluxModelAssetDetailCustomization.h"
|
||||||
|
#include "Assets/DTFluxModelAsset.h"
|
||||||
|
|
||||||
#define LOCTEXT_NAMESPACE "FDTFluxAssetsEditorModule"
|
#define LOCTEXT_NAMESPACE "FDTFluxAssetsEditorModule"
|
||||||
|
|
||||||
|
|
||||||
DTFLUXASSETSEDITOR_API DEFINE_LOG_CATEGORY(logDTFluxAssetEditor)
|
DTFLUXASSETSEDITOR_API DEFINE_LOG_CATEGORY(logDTFluxAssetEditor)
|
||||||
|
|
||||||
void FDTFluxAssetsEditorModule::StartupModule()
|
void FDTFluxAssetsEditorModule::StartupModule()
|
||||||
{
|
{
|
||||||
IAssetTools& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
IAssetTools& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||||||
EAssetTypeCategories::Type Category = AssetToolsModule.RegisterAdvancedAssetCategory("DTFlux", INVTEXT("DTFlux"));
|
EAssetTypeCategories::Type Category = AssetToolsModule.RegisterAdvancedAssetCategory("DTFlux", INVTEXT("DTFlux"));
|
||||||
DTFluxAssetModelActions = MakeShareable(new FDTFluxAssetModelTypeActions(Category));
|
DTFluxAssetModelActions = MakeShareable(new FDTFluxAssetModelTypeActions(Category));
|
||||||
AssetToolsModule.RegisterAssetTypeActions(DTFluxAssetModelActions.ToSharedRef());
|
AssetToolsModule.RegisterAssetTypeActions(DTFluxAssetModelActions.ToSharedRef());
|
||||||
|
RegisterCustomizations();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FDTFluxAssetsEditorModule::ShutdownModule()
|
void FDTFluxAssetsEditorModule::ShutdownModule()
|
||||||
@ -23,6 +27,34 @@ void FDTFluxAssetsEditorModule::ShutdownModule()
|
|||||||
IAssetTools& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
IAssetTools& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||||||
AssetToolsModule.UnregisterAssetTypeActions(DTFluxAssetModelActions.ToSharedRef());
|
AssetToolsModule.UnregisterAssetTypeActions(DTFluxAssetModelActions.ToSharedRef());
|
||||||
}
|
}
|
||||||
|
UnregisterCustomizations();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FDTFluxAssetsEditorModule::RegisterCustomizations()
|
||||||
|
{
|
||||||
|
// Enregistrer la customization pour DTFluxModelAsset
|
||||||
|
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||||||
|
|
||||||
|
PropertyModule.RegisterCustomClassLayout(
|
||||||
|
UDTFluxModelAsset::StaticClass()->GetFName(),
|
||||||
|
FOnGetDetailCustomizationInstance::CreateStatic(&FDTFluxModelAssetCustomization::MakeInstance)
|
||||||
|
);
|
||||||
|
|
||||||
|
PropertyModule.NotifyCustomizationModuleChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxAssetsEditorModule::UnregisterCustomizations()
|
||||||
|
{
|
||||||
|
if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
|
||||||
|
{
|
||||||
|
FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>(
|
||||||
|
"PropertyEditor");
|
||||||
|
|
||||||
|
PropertyModule.UnregisterCustomClassLayout(UDTFluxModelAsset::StaticClass()->GetFName());
|
||||||
|
|
||||||
|
PropertyModule.NotifyCustomizationModuleChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef LOCTEXT_NAMESPACE
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|||||||
@ -0,0 +1,222 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#include "DTFluxModelAssetDetailCustomization.h"
|
||||||
|
#include "DetailLayoutBuilder.h"
|
||||||
|
#include "DetailCategoryBuilder.h"
|
||||||
|
#include "DetailWidgetRow.h"
|
||||||
|
#include "Widgets/Layout/SBox.h"
|
||||||
|
#include "Assets/DTFluxModelAsset.h"
|
||||||
|
|
||||||
|
|
||||||
|
TSharedRef<IDetailCustomization> FDTFluxModelAssetCustomization::MakeInstance()
|
||||||
|
{
|
||||||
|
return MakeShareable(new FDTFluxModelAssetCustomization);
|
||||||
|
}
|
||||||
|
|
||||||
|
EActiveTimerReturnType SDTFluxAssetModelDetailsWidget::ForceInitialLayout(double InCurrentTime, float InDeltaTime)
|
||||||
|
{
|
||||||
|
// Forcer la mise à jour des TreeViews
|
||||||
|
if (ContestTreeView.IsValid())
|
||||||
|
{
|
||||||
|
ContestTreeView->RequestTreeRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ParticipantTreeView.IsValid())
|
||||||
|
{
|
||||||
|
ParticipantTreeView->RequestTreeRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forcer l'invalidation du layout
|
||||||
|
Invalidate(EInvalidateWidget::Layout);
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("Forced initial layout refresh"));
|
||||||
|
|
||||||
|
// Arrêter le timer (exécution unique)
|
||||||
|
return EActiveTimerReturnType::Stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxModelAssetCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
|
||||||
|
{
|
||||||
|
CustomizeDetailsWithRawDataAccess(DetailBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxModelAssetCustomization::CustomizeDetailsWithoutRawDataAsset(IDetailLayoutBuilder& DetailBuilder)
|
||||||
|
{
|
||||||
|
// Edit object
|
||||||
|
TArray<TWeakObjectPtr<UObject>> ObjectsBeingCustomized;
|
||||||
|
DetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized);
|
||||||
|
|
||||||
|
if (ObjectsBeingCustomized.Num() > 0)
|
||||||
|
{
|
||||||
|
ModelAsset = Cast<UDTFluxModelAsset>(ObjectsBeingCustomized[0].Get());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ModelAsset.IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Error, TEXT("No valid DTFluxModelAsset found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Hiding Categories/Props =====
|
||||||
|
DetailBuilder.HideCategory("General");
|
||||||
|
DetailBuilder.HideCategory("Default");
|
||||||
|
DetailBuilder.HideCategory("Transform");
|
||||||
|
DetailBuilder.HideCategory("Rendering");
|
||||||
|
DetailBuilder.HideCategory("Input");
|
||||||
|
DetailBuilder.HideCategory("Actor");
|
||||||
|
DetailBuilder.HideCategory("Advanced");
|
||||||
|
// Hide individual Props
|
||||||
|
DetailBuilder.HideProperty("EventName");
|
||||||
|
DetailBuilder.HideProperty("Persons");
|
||||||
|
DetailBuilder.HideProperty("Participants");
|
||||||
|
DetailBuilder.HideProperty("Contests");
|
||||||
|
DetailBuilder.HideProperty("ContestRankings");
|
||||||
|
DetailBuilder.HideProperty("StageRankings");
|
||||||
|
DetailBuilder.HideProperty("SplitRankings");
|
||||||
|
|
||||||
|
IDetailCategoryBuilder& MainCategory = DetailBuilder.EditCategory(
|
||||||
|
"DTFlux Model Explorer",
|
||||||
|
FText::FromString("DTFlux Model Explorer"),
|
||||||
|
ECategoryPriority::Important
|
||||||
|
);
|
||||||
|
|
||||||
|
// Créer le widget hiérarchique
|
||||||
|
DetailsWidget = SNew(SDTFluxAssetModelDetailsWidget)
|
||||||
|
.ModelAsset(ModelAsset.Get());
|
||||||
|
|
||||||
|
MainCategory.AddCustomRow(FText::FromString("Data Explorer"))
|
||||||
|
.WholeRowContent()
|
||||||
|
[
|
||||||
|
SNew(SBox)
|
||||||
|
.MinDesiredHeight(800.0f)
|
||||||
|
[
|
||||||
|
DetailsWidget.ToSharedRef()
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("DTFluxModelAsset custom-only interface applied"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FDTFluxModelAssetCustomization::CustomizeDetailsWithRawDataAccess(IDetailLayoutBuilder& DetailBuilder)
|
||||||
|
{
|
||||||
|
// ===== WIDGET PRINCIPAL =====
|
||||||
|
IDetailCategoryBuilder& MainCategory = DetailBuilder.EditCategory(
|
||||||
|
"DTFlux Model Explorer",
|
||||||
|
FText::FromString("DTFlux Model Explorer"),
|
||||||
|
ECategoryPriority::Important
|
||||||
|
);
|
||||||
|
|
||||||
|
DetailsWidget = SNew(SDTFluxAssetModelDetailsWidget)
|
||||||
|
.ModelAsset(ModelAsset.Get());
|
||||||
|
|
||||||
|
MainCategory.AddCustomRow(FText::FromString("Data Explorer"))
|
||||||
|
.WholeRowContent()
|
||||||
|
[
|
||||||
|
SNew(SBox)
|
||||||
|
.MinDesiredHeight(650.0f)
|
||||||
|
|
||||||
|
[
|
||||||
|
DetailsWidget.ToSharedRef()
|
||||||
|
]
|
||||||
|
];
|
||||||
|
IDetailCategoryBuilder& QuickActionsCategory = DetailBuilder.EditCategory(
|
||||||
|
"Quick Actions",
|
||||||
|
FText::FromString("Quick Actions"),
|
||||||
|
ECategoryPriority::Default
|
||||||
|
);
|
||||||
|
|
||||||
|
QuickActionsCategory.AddCustomRow(FText::FromString("Raw Data Access"))
|
||||||
|
.NameContent()
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(FText::FromString("Raw Data Access"))
|
||||||
|
.Font(FAppStyle::GetFontStyle("NormalText"))
|
||||||
|
]
|
||||||
|
.ValueContent()
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.AutoWidth()
|
||||||
|
.Padding(0, 0, 5, 0)
|
||||||
|
[
|
||||||
|
SNew(SButton)
|
||||||
|
.ButtonStyle(FAppStyle::Get(), "Button")
|
||||||
|
.Text(FText::FromString("Edit Raw Properties"))
|
||||||
|
.ToolTipText(FText::FromString("Temporarily show standard properties for advanced editing"))
|
||||||
|
.OnClicked_Lambda([this, &DetailBuilder]() -> FReply
|
||||||
|
{
|
||||||
|
// Forcer le rafraîchissement du DetailsPanel pour montrer les propriétés standard
|
||||||
|
DetailBuilder.ForceRefreshDetails();
|
||||||
|
UE_LOG(LogTemp, Warning,
|
||||||
|
TEXT(
|
||||||
|
"Tip: To edit raw data, right-click the asset and choose 'Edit' or use the Content Browser"
|
||||||
|
));
|
||||||
|
return FReply::Handled();
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.AutoWidth()
|
||||||
|
.Padding(0, 0, 5, 0)
|
||||||
|
[
|
||||||
|
SNew(SButton)
|
||||||
|
.ButtonStyle(FAppStyle::Get(), "Button")
|
||||||
|
.Text(FText::FromString("Log All Data"))
|
||||||
|
.ToolTipText(FText::FromString("Print all data to Output Log"))
|
||||||
|
.OnClicked_Lambda([this]() -> FReply
|
||||||
|
{
|
||||||
|
if (ModelAsset.IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("=== DTFLUX MODEL DUMP ==="));
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("Event: %s"), *ModelAsset->EventName);
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("--- CONTESTS (%d) ---"), ModelAsset->Contests.Num());
|
||||||
|
for (const auto& Contest : ModelAsset->Contests)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("Contest '%s' (ID: %d) - %d stages, %d participants"),
|
||||||
|
*Contest.Key, Contest.Value.ContestId,
|
||||||
|
Contest.Value.Stages.Num(), Contest.Value.ParticipantsBib.Num());
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("--- PARTICIPANTS (%d) ---"), ModelAsset->Participants.Num());
|
||||||
|
for (const auto& Participant : ModelAsset->Participants)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("Bib %d: %s (%s) - %d teammates"),
|
||||||
|
Participant.Value.Bib, *Participant.Value.Team,
|
||||||
|
*Participant.Value.Category, Participant.Value.GetTeammate().Num());
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("--- PERSONS (%d) ---"), ModelAsset->Persons.Num());
|
||||||
|
for (int32 i = 0; i < ModelAsset->Persons.Num(); ++i)
|
||||||
|
{
|
||||||
|
const auto& Person = ModelAsset->Persons[i];
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("Person %d: %s %s (%s)"),
|
||||||
|
i, *Person.FirstName, *Person.LastName, *Person.Gender);
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("======================="));
|
||||||
|
}
|
||||||
|
return FReply::Handled();
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.AutoWidth()
|
||||||
|
[
|
||||||
|
SNew(SButton)
|
||||||
|
.ButtonStyle(FAppStyle::Get(), "PrimaryButton")
|
||||||
|
.Text(FText::FromString("Refresh"))
|
||||||
|
.ToolTipText(FText::FromString("Refresh the hierarchy view"))
|
||||||
|
.OnClicked_Lambda([this]() -> FReply
|
||||||
|
{
|
||||||
|
if (DetailsWidget.IsValid())
|
||||||
|
{
|
||||||
|
DetailsWidget->RefreshData();
|
||||||
|
}
|
||||||
|
return FReply::Handled();
|
||||||
|
})
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
@ -0,0 +1,645 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
#include "Widget/DTFluxAssetModelDetailsWidget.h"
|
||||||
|
|
||||||
|
#include "Widgets/Layout/SBorder.h"
|
||||||
|
#include "Widgets/Layout/SBox.h"
|
||||||
|
#include "Widgets/Text/STextBlock.h"
|
||||||
|
#include "Widgets/Input/SButton.h"
|
||||||
|
#include "Widgets/Views/STableRow.h"
|
||||||
|
#include "Widgets/Views/SHeaderRow.h"
|
||||||
|
|
||||||
|
void SHierarchicalTreeItemRow::Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView)
|
||||||
|
{
|
||||||
|
Item = InArgs._Item;
|
||||||
|
ParentWidget = InArgs._ParentWidget;
|
||||||
|
|
||||||
|
SMultiColumnTableRow<TSharedPtr<FHierarchicalTreeItem>>::Construct(
|
||||||
|
SMultiColumnTableRow<TSharedPtr<FHierarchicalTreeItem>>::FArguments(),
|
||||||
|
InOwnerTableView
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedRef<SWidget> SHierarchicalTreeItemRow::GenerateWidgetForColumn(const FName& ColumnName)
|
||||||
|
{
|
||||||
|
if (!Item.IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("GenerateWidgetForColumn: Invalid item for column %s"), *ColumnName.ToString());
|
||||||
|
return SNew(STextBlock).Text(FText::FromString("Invalid Item"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ParentWidget)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("GenerateWidgetForColumn: Invalid ParentWidget for column %s"),
|
||||||
|
*ColumnName.ToString());
|
||||||
|
return SNew(STextBlock).Text(FText::FromString("Invalid Parent"));
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, VeryVerbose, TEXT("GenerateWidgetForColumn: %s for item %s"),
|
||||||
|
*ColumnName.ToString(), *Item->Name);
|
||||||
|
|
||||||
|
if (ColumnName == "Name")
|
||||||
|
{
|
||||||
|
return SNew(SHorizontalBox)
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.AutoWidth()
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
.Padding(0, 0, 5, 0)
|
||||||
|
[
|
||||||
|
SNew(SImage)
|
||||||
|
.Image(ParentWidget->GetItemIcon(Item->Type))
|
||||||
|
.ColorAndOpacity(ParentWidget->GetItemTypeColor(Item->Type))
|
||||||
|
]
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.FillWidth(1.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(FText::FromString(Item->Name))
|
||||||
|
.ColorAndOpacity(ParentWidget->GetItemTypeColor(Item->Type))
|
||||||
|
.Font_Lambda([this]() -> FSlateFontInfo
|
||||||
|
{
|
||||||
|
return Item->Type == FHierarchicalTreeItem::EItemType::Contest
|
||||||
|
? FAppStyle::GetFontStyle("HeadingText")
|
||||||
|
: FAppStyle::GetFontStyle("NormalText");
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
|
else if (ColumnName == "ID")
|
||||||
|
{
|
||||||
|
return SNew(STextBlock)
|
||||||
|
.Text(FText::FromString(Item->ID))
|
||||||
|
.Font(FAppStyle::GetFontStyle("NormalText"))
|
||||||
|
.Justification(ETextJustify::Center);
|
||||||
|
}
|
||||||
|
else if (ColumnName == "Details")
|
||||||
|
{
|
||||||
|
return SNew(STextBlock)
|
||||||
|
.Text(FText::FromString(Item->Details))
|
||||||
|
.Font(FAppStyle::GetFontStyle("NormalText"))
|
||||||
|
.OverflowPolicy(ETextOverflowPolicy::Ellipsis);
|
||||||
|
}
|
||||||
|
else if (ColumnName == "Status")
|
||||||
|
{
|
||||||
|
return SNew(STextBlock)
|
||||||
|
.Text(FText::FromString(Item->Status))
|
||||||
|
.Font(FAppStyle::GetFontStyle("NormalText"))
|
||||||
|
.OverflowPolicy(ETextOverflowPolicy::Ellipsis);
|
||||||
|
}
|
||||||
|
else if (ColumnName == "Extra")
|
||||||
|
{
|
||||||
|
return SNew(STextBlock)
|
||||||
|
.Text(FText::FromString(Item->Extra))
|
||||||
|
.Font(FAppStyle::GetFontStyle("NormalText"))
|
||||||
|
.OverflowPolicy(ETextOverflowPolicy::Ellipsis);
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("GenerateWidgetForColumn: Unknown column %s"), *ColumnName.ToString());
|
||||||
|
return SNew(STextBlock).Text(FText::FromString(FString::Printf(TEXT("Unknown: %s"), *ColumnName.ToString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxAssetModelDetailsWidget::Construct(const FArguments& InArgs)
|
||||||
|
{
|
||||||
|
ModelAsset = InArgs._ModelAsset;
|
||||||
|
|
||||||
|
ChildSlot
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
||||||
|
[
|
||||||
|
SNew(SVerticalBox)
|
||||||
|
|
||||||
|
// === SECTION STATISTIQUES ===
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
.Padding(5)
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
||||||
|
.Padding(10)
|
||||||
|
[
|
||||||
|
SAssignNew(StatsText, STextBlock)
|
||||||
|
.Text(this, &SDTFluxAssetModelDetailsWidget::GetStatsText)
|
||||||
|
.Font(FAppStyle::GetFontStyle("HeadingText"))
|
||||||
|
.Justification(ETextJustify::Center)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
// === SECTION BOUTONS DE NAVIGATION ===
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
.Padding(5)
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.AutoWidth()
|
||||||
|
.Padding(0, 0, 5, 0)
|
||||||
|
[
|
||||||
|
SNew(SButton)
|
||||||
|
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
|
||||||
|
.Text(FText::FromString("Expand All Contests"))
|
||||||
|
.OnClicked(this, &SDTFluxAssetModelDetailsWidget::OnExpandAllClicked)
|
||||||
|
]
|
||||||
|
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.AutoWidth()
|
||||||
|
.Padding(0, 0, 10, 0)
|
||||||
|
[
|
||||||
|
SNew(SButton)
|
||||||
|
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
|
||||||
|
.Text(FText::FromString("Collapse All Contests"))
|
||||||
|
.OnClicked(this, &SDTFluxAssetModelDetailsWidget::OnCollapseAllClicked)
|
||||||
|
]
|
||||||
|
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.FillWidth(1.0f)
|
||||||
|
[
|
||||||
|
SNew(SSpacer)
|
||||||
|
]
|
||||||
|
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.AutoWidth()
|
||||||
|
[
|
||||||
|
SNew(SButton)
|
||||||
|
.ButtonStyle(FAppStyle::Get(), "PrimaryButton")
|
||||||
|
.Text(FText::FromString("Refresh"))
|
||||||
|
.OnClicked(this, &SDTFluxAssetModelDetailsWidget::OnRefreshClicked)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
#pragma region ListView
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.FillHeight(1.0f)
|
||||||
|
.Padding(5)
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
|
||||||
|
.Padding(0)
|
||||||
|
[
|
||||||
|
SNew(SBox)
|
||||||
|
[
|
||||||
|
#pragma region ScrollBox
|
||||||
|
SNew(SScrollBox)
|
||||||
|
.Orientation(Orient_Vertical)
|
||||||
|
.ScrollBarVisibility(EVisibility::Visible)
|
||||||
|
.ConsumeMouseWheel(EConsumeMouseWheel::WhenScrollingPossible)
|
||||||
|
.ScrollBarAlwaysVisible(true) // Force la scrollbar à être toujours visible
|
||||||
|
|
||||||
|
#pragma region ListView.Contest
|
||||||
|
+ SScrollBox::Slot()
|
||||||
|
.Padding(0, 0, 0, 10)
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
|
||||||
|
[
|
||||||
|
SNew(SVerticalBox)
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
.Padding(5)
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
|
||||||
|
.Padding(10, 5)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(FText::FromString("CONTESTS HIERARCHY"))
|
||||||
|
.Font(FAppStyle::GetFontStyle("HeadingText"))
|
||||||
|
.ColorAndOpacity(FLinearColor(0.2f, 0.6f, 1.0f))
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
// TreeView Contests
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
[
|
||||||
|
SNew(SBox)
|
||||||
|
[
|
||||||
|
SAssignNew(ContestTreeView, STreeView<FHierarchicalTreeItemPtr>)
|
||||||
|
.TreeItemsSource(&RootItems)
|
||||||
|
.OnGenerateRow(this, &SDTFluxAssetModelDetailsWidget::OnGenerateRowForTree)
|
||||||
|
.OnGetChildren(this, &SDTFluxAssetModelDetailsWidget::OnGetChildrenForTree)
|
||||||
|
.OnSelectionChanged(
|
||||||
|
this, &SDTFluxAssetModelDetailsWidget::OnTreeSelectionChanged)
|
||||||
|
.OnSetExpansionRecursive(
|
||||||
|
this, &SDTFluxAssetModelDetailsWidget::OnSetExpansionRecursive)
|
||||||
|
.SelectionMode(ESelectionMode::Single)
|
||||||
|
.ConsumeMouseWheel(EConsumeMouseWheel::WhenScrollingPossible)
|
||||||
|
.HeaderRow
|
||||||
|
(
|
||||||
|
SNew(SHeaderRow)
|
||||||
|
.CanSelectGeneratedColumn(true)
|
||||||
|
|
||||||
|
+ SHeaderRow::Column("Name")
|
||||||
|
.DefaultLabel(FText::FromString("Contest / Stage / Split"))
|
||||||
|
.SortMode(EColumnSortMode::None)
|
||||||
|
|
||||||
|
+ SHeaderRow::Column("ID")
|
||||||
|
.DefaultLabel(FText::FromString("ID"))
|
||||||
|
.SortMode(EColumnSortMode::None)
|
||||||
|
.FillWidth(.02f)
|
||||||
|
|
||||||
|
|
||||||
|
+ SHeaderRow::Column("Details")
|
||||||
|
.DefaultLabel(FText::FromString("Details"))
|
||||||
|
.SortMode(EColumnSortMode::None)
|
||||||
|
.FillWidth(.3f)
|
||||||
|
|
||||||
|
+ SHeaderRow::Column("Status")
|
||||||
|
.DefaultLabel(FText::FromString("Status / Time"))
|
||||||
|
.SortMode(EColumnSortMode::None)
|
||||||
|
.FillWidth(.1f)
|
||||||
|
|
||||||
|
|
||||||
|
+ SHeaderRow::Column("Extra")
|
||||||
|
.DefaultLabel(FText::FromString("Extra Info"))
|
||||||
|
.FillWidth(.2f)
|
||||||
|
|
||||||
|
.SortMode(EColumnSortMode::None)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
#pragma region ListView.Participant
|
||||||
|
+ SScrollBox::Slot()
|
||||||
|
.Padding(0, 10, 0, 0)
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
|
||||||
|
[
|
||||||
|
SNew(SVerticalBox)
|
||||||
|
|
||||||
|
// Header "Participants"
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
.Padding(5)
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
|
||||||
|
.Padding(10, 5)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(FText::FromString("PARTICIPANTS LIST"))
|
||||||
|
.Font(FAppStyle::GetFontStyle("HeadingText"))
|
||||||
|
.ColorAndOpacity(FLinearColor(0.2f, 0.8f, 0.8f))
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
// TreeView Participants
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
[
|
||||||
|
SNew(SBox)
|
||||||
|
[
|
||||||
|
SAssignNew(ParticipantTreeView, STreeView<FHierarchicalTreeItemPtr>)
|
||||||
|
.TreeItemsSource(&ParticipantItems)
|
||||||
|
.OnGenerateRow(this, &SDTFluxAssetModelDetailsWidget::OnGenerateRowForTree)
|
||||||
|
.OnGetChildren(this, &SDTFluxAssetModelDetailsWidget::OnGetChildrenForTree)
|
||||||
|
.OnSelectionChanged(
|
||||||
|
this, &SDTFluxAssetModelDetailsWidget::OnTreeSelectionChanged)
|
||||||
|
.OnSetExpansionRecursive(
|
||||||
|
this, &SDTFluxAssetModelDetailsWidget::OnSetExpansionRecursive)
|
||||||
|
.SelectionMode(ESelectionMode::Single)
|
||||||
|
.ConsumeMouseWheel(EConsumeMouseWheel::WhenScrollingPossible)
|
||||||
|
.HeaderRow
|
||||||
|
(
|
||||||
|
SNew(SHeaderRow)
|
||||||
|
.CanSelectGeneratedColumn(true)
|
||||||
|
|
||||||
|
+ SHeaderRow::Column("Name")
|
||||||
|
.DefaultLabel(FText::FromString("Participant"))
|
||||||
|
.SortMode(EColumnSortMode::None)
|
||||||
|
|
||||||
|
+ SHeaderRow::Column("ID")
|
||||||
|
.DefaultLabel(FText::FromString("Bib"))
|
||||||
|
.SortMode(EColumnSortMode::None)
|
||||||
|
.FillWidth(.08f)
|
||||||
|
|
||||||
|
+ SHeaderRow::Column("Details")
|
||||||
|
.DefaultLabel(FText::FromString("Category & Teammates"))
|
||||||
|
.SortMode(EColumnSortMode::None)
|
||||||
|
.FillWidth(.2f)
|
||||||
|
|
||||||
|
+ SHeaderRow::Column("Status")
|
||||||
|
.DefaultLabel(FText::FromString("Status"))
|
||||||
|
.FillWidth(.1f)
|
||||||
|
.SortMode(EColumnSortMode::None)
|
||||||
|
|
||||||
|
+ SHeaderRow::Column("Extra")
|
||||||
|
.DefaultLabel(FText::FromString("Club"))
|
||||||
|
.FillWidth(.2f)
|
||||||
|
.SortMode(EColumnSortMode::None)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
#pragma endregion
|
||||||
|
]
|
||||||
|
#pragma endregion
|
||||||
|
]
|
||||||
|
#pragma endregion
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
#pragma region DetailView.Participant
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
.Padding(5)
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
|
||||||
|
.Padding(10)
|
||||||
|
[
|
||||||
|
SAssignNew(SelectionText, STextBlock)
|
||||||
|
.Text(FText::FromString("Select an item to see details. Use expand/collapse arrows in the tree."))
|
||||||
|
.Font(FAppStyle::GetFontStyle("NormalText"))
|
||||||
|
.AutoWrapText(true)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
#pragma endregion
|
||||||
|
]
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
RefreshData();
|
||||||
|
|
||||||
|
RegisterActiveTimer(
|
||||||
|
0.1f, FWidgetActiveTimerDelegate::CreateSP(this, &SDTFluxAssetModelDetailsWidget::ForceInitialLayout));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== CONSTRUCTION DE LA HIÉRARCHIE =====
|
||||||
|
|
||||||
|
void SDTFluxAssetModelDetailsWidget::BuildContestHierarchy()
|
||||||
|
{
|
||||||
|
RootItems.Empty();
|
||||||
|
|
||||||
|
if (!ModelAsset)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Construire la hiérarchie Contest → Stages → Splits
|
||||||
|
for (const auto& ContestPair : ModelAsset->Contests)
|
||||||
|
{
|
||||||
|
const FString& ContestName = ContestPair.Key;
|
||||||
|
const FDTFluxContest& Contest = ContestPair.Value;
|
||||||
|
|
||||||
|
// Créer l'élément Contest racine
|
||||||
|
auto ContestItem = FHierarchicalTreeItem::CreateContest(ContestName, Contest);
|
||||||
|
|
||||||
|
// Ajouter les Stages comme enfants
|
||||||
|
for (const FDTFluxStage& Stage : Contest.Stages)
|
||||||
|
{
|
||||||
|
auto StageItem = FHierarchicalTreeItem::CreateStage(Stage, Contest.ContestId);
|
||||||
|
ContestItem->AddChild(StageItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajouter les Splits comme enfants directs du Contest
|
||||||
|
for (const FDTFluxSplit& Split : Contest.Splits)
|
||||||
|
{
|
||||||
|
auto SplitItem = FHierarchicalTreeItem::CreateSplit(Split, Contest.ContestId);
|
||||||
|
ContestItem->AddChild(SplitItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
RootItems.Add(ContestItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("Built contest hierarchy with %d root contests"), RootItems.Num());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxAssetModelDetailsWidget::BuildParticipantList()
|
||||||
|
{
|
||||||
|
ParticipantItems.Empty();
|
||||||
|
|
||||||
|
if (!ModelAsset)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("BuildParticipantList: ModelAsset is null!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("BuildParticipantList: ModelAsset has %d participants"), ModelAsset->Participants.Num());
|
||||||
|
|
||||||
|
// Créer la liste des participants (pas de hiérarchie pour les participants)
|
||||||
|
for (const auto& ParticipantPair : ModelAsset->Participants)
|
||||||
|
{
|
||||||
|
const FDTFluxParticipant& Participant = ParticipantPair.Value;
|
||||||
|
auto ParticipantItem = FHierarchicalTreeItem::CreateParticipant(Participant);
|
||||||
|
ParticipantItems.Add(ParticipantItem);
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("BuildParticipantList: Added participant %s (Bib: %d)"),
|
||||||
|
*ParticipantItem->Name, ParticipantItem->Bib);
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("BuildParticipantList: Built participant list with %d participants"),
|
||||||
|
ParticipantItems.Num());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== CALLBACKS TREEVIEW =====
|
||||||
|
|
||||||
|
TSharedRef<ITableRow> SDTFluxAssetModelDetailsWidget::OnGenerateRowForTree(
|
||||||
|
FHierarchicalTreeItemPtr Item, const TSharedRef<STableViewBase>& OwnerTable)
|
||||||
|
{
|
||||||
|
if (!Item.IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("OnGenerateRowForTree: Invalid item!"));
|
||||||
|
return SNew(STableRow<FHierarchicalTreeItemPtr>, OwnerTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("OnGenerateRowForTree: Generating row for %s (Type: %d)"),
|
||||||
|
*Item->Name, (int32)Item->Type);
|
||||||
|
|
||||||
|
return SNew(SHierarchicalTreeItemRow, OwnerTable)
|
||||||
|
.Item(Item)
|
||||||
|
.ParentWidget(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxAssetModelDetailsWidget::OnGetChildrenForTree(FHierarchicalTreeItemPtr Item,
|
||||||
|
TArray<FHierarchicalTreeItemPtr>& OutChildren)
|
||||||
|
{
|
||||||
|
if (Item.IsValid())
|
||||||
|
{
|
||||||
|
OutChildren = Item->Children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxAssetModelDetailsWidget::OnTreeSelectionChanged(FHierarchicalTreeItemPtr SelectedItem,
|
||||||
|
ESelectInfo::Type SelectInfo)
|
||||||
|
{
|
||||||
|
if (SelectionText.IsValid())
|
||||||
|
{
|
||||||
|
if (SelectedItem.IsValid())
|
||||||
|
{
|
||||||
|
FString TypeString;
|
||||||
|
switch (SelectedItem->Type)
|
||||||
|
{
|
||||||
|
case FHierarchicalTreeItem::EItemType::Contest:
|
||||||
|
TypeString = "Contest";
|
||||||
|
break;
|
||||||
|
case FHierarchicalTreeItem::EItemType::Stage:
|
||||||
|
TypeString = "Stage";
|
||||||
|
break;
|
||||||
|
case FHierarchicalTreeItem::EItemType::Split:
|
||||||
|
TypeString = "Split";
|
||||||
|
break;
|
||||||
|
case FHierarchicalTreeItem::EItemType::Participant:
|
||||||
|
TypeString = "Participant";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
FString SelectionInfo = FString::Printf(
|
||||||
|
TEXT("📋 Selected: %s (%s)\n🔢 ID: %s\n📄 Details: %s\n📊 Status: %s\n➕ Extra: %s\n🌟 Children: %d"),
|
||||||
|
*SelectedItem->Name,
|
||||||
|
*TypeString,
|
||||||
|
*SelectedItem->ID,
|
||||||
|
*SelectedItem->Details,
|
||||||
|
*SelectedItem->Status,
|
||||||
|
*SelectedItem->Extra,
|
||||||
|
SelectedItem->Children.Num());
|
||||||
|
SelectionText->SetText(FText::FromString(SelectionInfo));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectionText->SetText(FText::FromString("Select an item to see details"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxAssetModelDetailsWidget::OnSetExpansionRecursive(FHierarchicalTreeItemPtr Item, bool bIsExpanded)
|
||||||
|
{
|
||||||
|
if (Item.IsValid() && ContestTreeView.IsValid())
|
||||||
|
{
|
||||||
|
ContestTreeView->SetItemExpansion(Item, bIsExpanded);
|
||||||
|
|
||||||
|
// Expansion récursive des enfants
|
||||||
|
for (auto Child : Item->Children)
|
||||||
|
{
|
||||||
|
OnSetExpansionRecursive(Child, bIsExpanded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== CALLBACKS DES BOUTONS =====
|
||||||
|
|
||||||
|
FReply SDTFluxAssetModelDetailsWidget::OnExpandAllClicked()
|
||||||
|
{
|
||||||
|
if (ContestTreeView.IsValid())
|
||||||
|
{
|
||||||
|
for (auto& RootItem : RootItems)
|
||||||
|
{
|
||||||
|
OnSetExpansionRecursive(RootItem, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("Expanded all contests"));
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
FReply SDTFluxAssetModelDetailsWidget::OnCollapseAllClicked()
|
||||||
|
{
|
||||||
|
if (ContestTreeView.IsValid())
|
||||||
|
{
|
||||||
|
for (auto& RootItem : RootItems)
|
||||||
|
{
|
||||||
|
OnSetExpansionRecursive(RootItem, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("Collapsed all contests"));
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
FReply SDTFluxAssetModelDetailsWidget::OnRefreshClicked()
|
||||||
|
{
|
||||||
|
RefreshData();
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("Data refreshed"));
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== REFRESHDATA =====
|
||||||
|
|
||||||
|
void SDTFluxAssetModelDetailsWidget::RefreshData()
|
||||||
|
{
|
||||||
|
if (!ModelAsset)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("ModelAsset is null!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("RefreshData: Starting refresh for ModelAsset %s"), *ModelAsset->GetName());
|
||||||
|
|
||||||
|
// Nettoyer les données existantes
|
||||||
|
RootItems.Empty();
|
||||||
|
ParticipantItems.Empty();
|
||||||
|
|
||||||
|
// Construire la hiérarchie
|
||||||
|
BuildContestHierarchy();
|
||||||
|
BuildParticipantList();
|
||||||
|
|
||||||
|
// Refresh les vues
|
||||||
|
if (ContestTreeView.IsValid())
|
||||||
|
{
|
||||||
|
ContestTreeView->RequestTreeRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ParticipantTreeView.IsValid())
|
||||||
|
{
|
||||||
|
ParticipantTreeView->RequestTreeRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("RefreshData: Completed successfully - %d contests, %d participants"), RootItems.Num(),
|
||||||
|
ParticipantItems.Num());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== MÉTHODES UTILITAIRES =====
|
||||||
|
|
||||||
|
FSlateColor SDTFluxAssetModelDetailsWidget::GetItemTypeColor(FHierarchicalTreeItem::EItemType Type) const
|
||||||
|
{
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
case FHierarchicalTreeItem::EItemType::Contest:
|
||||||
|
return FSlateColor(FLinearColor(0.2f, 0.6f, 1.0f));
|
||||||
|
case FHierarchicalTreeItem::EItemType::Stage:
|
||||||
|
return FSlateColor(FLinearColor(0.2f, 0.8f, 0.2f));
|
||||||
|
case FHierarchicalTreeItem::EItemType::Split:
|
||||||
|
return FSlateColor(FLinearColor(1.0f, 0.8f, 0.2f));
|
||||||
|
case FHierarchicalTreeItem::EItemType::Participant:
|
||||||
|
return FSlateColor(FLinearColor(0.2f, 0.8f, 0.8f));
|
||||||
|
default:
|
||||||
|
return FSlateColor(FLinearColor::White);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FSlateBrush* SDTFluxAssetModelDetailsWidget::GetItemIcon(FHierarchicalTreeItem::EItemType Type) const
|
||||||
|
{
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
case FHierarchicalTreeItem::EItemType::Contest:
|
||||||
|
return FAppStyle::GetBrush("TreeArrow_Collapsed");
|
||||||
|
case FHierarchicalTreeItem::EItemType::Stage:
|
||||||
|
case FHierarchicalTreeItem::EItemType::Split:
|
||||||
|
return FAppStyle::GetBrush("TreeArrow_Expanded");
|
||||||
|
case FHierarchicalTreeItem::EItemType::Participant:
|
||||||
|
return FAppStyle::GetBrush("Icons.User");
|
||||||
|
default:
|
||||||
|
return FAppStyle::GetBrush("Icons.Help");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FText SDTFluxAssetModelDetailsWidget::GetStatsText() const
|
||||||
|
{
|
||||||
|
if (!ModelAsset)
|
||||||
|
return FText::FromString("No data");
|
||||||
|
|
||||||
|
return FText::FromString(FString::Printf(
|
||||||
|
TEXT("Contests: [%d] Participants: [%d] Persons: [%d]"),
|
||||||
|
ModelAsset->Contests.Num(),
|
||||||
|
ModelAsset->Participants.Num(),
|
||||||
|
ModelAsset->Persons.Num()
|
||||||
|
));
|
||||||
|
}
|
||||||
@ -26,4 +26,6 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
TSharedPtr<FDTFluxAssetModelTypeActions> DTFluxAssetModelActions;
|
TSharedPtr<FDTFluxAssetModelTypeActions> DTFluxAssetModelActions;
|
||||||
|
void RegisterCustomizations();
|
||||||
|
void UnregisterCustomizations();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "IDetailCustomization.h"
|
||||||
|
#include "Widget/DTFluxAssetModelDetailsWidget.h"
|
||||||
|
|
||||||
|
class FDTFluxModelAssetCustomization : public IDetailCustomization
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// IDetailCustomization interface
|
||||||
|
virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
|
||||||
|
|
||||||
|
void CustomizeDetailsWithoutRawDataAsset(IDetailLayoutBuilder& DetailBuilder);
|
||||||
|
void CustomizeDetailsWithRawDataAccess(IDetailLayoutBuilder& DetailBuilder);
|
||||||
|
|
||||||
|
// Crée une instance de cette customization
|
||||||
|
static TSharedRef<IDetailCustomization> MakeInstance();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Handle vers l'objet en cours d'édition
|
||||||
|
TWeakObjectPtr<UDTFluxModelAsset> ModelAsset;
|
||||||
|
|
||||||
|
|
||||||
|
// Widget personnalisé
|
||||||
|
TSharedPtr<SDTFluxAssetModelDetailsWidget> DetailsWidget;
|
||||||
|
};
|
||||||
@ -0,0 +1,191 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
||||||
|
#include "Widgets/Views/STreeView.h"
|
||||||
|
#include "Widgets/Views/SHeaderRow.h"
|
||||||
|
#include "Assets/DTFluxModelAsset.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct FHierarchicalTreeItem;
|
||||||
|
class SDTFluxAssetModelDetailsWidget;
|
||||||
|
|
||||||
|
// ✅ SOUS-CLASSE DE SMultiColumnTableRow
|
||||||
|
class SHierarchicalTreeItemRow : public SMultiColumnTableRow<TSharedPtr<FHierarchicalTreeItem>>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SLATE_BEGIN_ARGS(SHierarchicalTreeItemRow)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SLATE_ARGUMENT(TSharedPtr<FHierarchicalTreeItem>, Item)
|
||||||
|
SLATE_ARGUMENT(SDTFluxAssetModelDetailsWidget*, ParentWidget)
|
||||||
|
SLATE_END_ARGS()
|
||||||
|
|
||||||
|
void Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual TSharedRef<SWidget> GenerateWidgetForColumn(const FName& ColumnName) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TSharedPtr<FHierarchicalTreeItem> Item;
|
||||||
|
SDTFluxAssetModelDetailsWidget* ParentWidget;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ✅ STRUCTURE SIMPLE POUR TREEVIEW
|
||||||
|
struct FHierarchicalTreeItem
|
||||||
|
{
|
||||||
|
enum class EItemType
|
||||||
|
{
|
||||||
|
Contest,
|
||||||
|
Stage,
|
||||||
|
Split,
|
||||||
|
Participant
|
||||||
|
};
|
||||||
|
|
||||||
|
EItemType Type;
|
||||||
|
FString Name;
|
||||||
|
FString ID;
|
||||||
|
FString Details;
|
||||||
|
FString Status;
|
||||||
|
FString Extra;
|
||||||
|
|
||||||
|
// Données pour retrouver l'élément original
|
||||||
|
int32 ContestId = -1;
|
||||||
|
int32 StageId = -1;
|
||||||
|
int32 SplitId = -1;
|
||||||
|
int32 Bib = -1;
|
||||||
|
|
||||||
|
// Enfants pour la hiérarchie
|
||||||
|
TArray<TSharedPtr<FHierarchicalTreeItem>> Children;
|
||||||
|
|
||||||
|
FHierarchicalTreeItem(EItemType InType, const FString& InName)
|
||||||
|
: Type(InType), Name(InName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddChild(TSharedPtr<FHierarchicalTreeItem> Child)
|
||||||
|
{
|
||||||
|
if (Child.IsValid())
|
||||||
|
{
|
||||||
|
Children.Add(Child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Factory methods pour créer les éléments
|
||||||
|
static TSharedPtr<FHierarchicalTreeItem> CreateContest(const FString& ContestName, const FDTFluxContest& Contest)
|
||||||
|
{
|
||||||
|
TSharedPtr<FHierarchicalTreeItem> Item = MakeShareable(
|
||||||
|
new FHierarchicalTreeItem(EItemType::Contest, ContestName));
|
||||||
|
Item->ContestId = Contest.ContestId;
|
||||||
|
Item->ID = FString::Printf(TEXT("%d"), Contest.ContestId);
|
||||||
|
Item->Details = FString::Printf(
|
||||||
|
TEXT("%d stages, %d participants"), Contest.Stages.Num(), Contest.ParticipantsBib.Num());
|
||||||
|
Item->Status = Contest.Date.ToString(TEXT("%Y-%m-%d"));
|
||||||
|
Item->Extra = Contest.IsFinished() ? TEXT("Finished") : TEXT("Active");
|
||||||
|
return Item;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TSharedPtr<FHierarchicalTreeItem> CreateStage(const FDTFluxStage& Stage, int32 InContestId)
|
||||||
|
{
|
||||||
|
FString StageName = Stage.Name.IsEmpty() ? FString::Printf(TEXT("Stage %d"), Stage.StageId) : Stage.Name;
|
||||||
|
TSharedPtr<FHierarchicalTreeItem> Item = MakeShareable(new FHierarchicalTreeItem(EItemType::Stage, StageName));
|
||||||
|
Item->ContestId = InContestId;
|
||||||
|
Item->StageId = Stage.StageId;
|
||||||
|
Item->ID = FString::Printf(TEXT("%d"), Stage.StageId);
|
||||||
|
Item->Details = FString::Printf(TEXT("Start: %s"), *Stage.StartTime.ToString(TEXT("%H:%M")));
|
||||||
|
Item->Status = FString::Printf(TEXT("End: %s"), *Stage.EndTime.ToString(TEXT("%H:%M")));
|
||||||
|
Item->Extra = FString::Printf(TEXT("Cutoff: %s"), *Stage.CutOff.ToString(TEXT("%H:%M")));
|
||||||
|
return Item;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TSharedPtr<FHierarchicalTreeItem> CreateSplit(const FDTFluxSplit& Split, int32 InContestId)
|
||||||
|
{
|
||||||
|
FString SplitName = Split.Name.IsEmpty() ? FString::Printf(TEXT("Split %d"), Split.SplitId) : Split.Name;
|
||||||
|
TSharedPtr<FHierarchicalTreeItem> Item = MakeShareable(new FHierarchicalTreeItem(EItemType::Split, SplitName));
|
||||||
|
Item->ContestId = InContestId;
|
||||||
|
Item->SplitId = Split.SplitId;
|
||||||
|
Item->ID = FString::Printf(TEXT("%d"), Split.SplitId);
|
||||||
|
Item->Details = FString::Printf(TEXT("%d rankings"), Split.SplitRankings.Num());
|
||||||
|
Item->Status = TEXT("-");
|
||||||
|
Item->Extra = TEXT("-");
|
||||||
|
return Item;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TSharedPtr<FHierarchicalTreeItem> CreateParticipant(const FDTFluxParticipant& Participant,
|
||||||
|
UDTFluxModelAsset* InModelAsset = nullptr)
|
||||||
|
{
|
||||||
|
FString ParticipantName = Participant.GetConcatFormattedName();
|
||||||
|
TSharedPtr<FHierarchicalTreeItem> Item = MakeShareable(
|
||||||
|
new FHierarchicalTreeItem(EItemType::Participant, ParticipantName));
|
||||||
|
Item->Bib = Participant.Bib;
|
||||||
|
Item->ContestId = Participant.ContestId;
|
||||||
|
Item->ID = FString::Printf(TEXT("%d"), Participant.Bib);
|
||||||
|
Item->Details = FString::Printf(TEXT("%s - %d teammates"), *Participant.Category,
|
||||||
|
Participant.GetTeammate().Num());
|
||||||
|
FString Status = UEnum::GetValueAsString(Participant.Status);
|
||||||
|
TArray<FString> StatusArray;
|
||||||
|
Status.ParseIntoArray(StatusArray, TEXT("::"));
|
||||||
|
Item->Status = StatusArray.Last();
|
||||||
|
Item->Extra = Participant.Club;
|
||||||
|
return Item;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef TSharedPtr<FHierarchicalTreeItem> FHierarchicalTreeItemPtr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Widget avec STreeView simple et efficace
|
||||||
|
*/
|
||||||
|
class DTFLUXASSETSEDITOR_API SDTFluxAssetModelDetailsWidget : public SCompoundWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SLATE_BEGIN_ARGS(SDTFluxAssetModelDetailsWidget)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SLATE_ARGUMENT(UDTFluxModelAsset*, ModelAsset)
|
||||||
|
SLATE_END_ARGS()
|
||||||
|
|
||||||
|
void Construct(const FArguments& InArgs);
|
||||||
|
void RefreshData();
|
||||||
|
|
||||||
|
// Méthodes publiques pour la sous-classe Row
|
||||||
|
FSlateColor GetItemTypeColor(FHierarchicalTreeItem::EItemType Type) const;
|
||||||
|
const FSlateBrush* GetItemIcon(FHierarchicalTreeItem::EItemType Type) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Données
|
||||||
|
UDTFluxModelAsset* ModelAsset = nullptr;
|
||||||
|
TArray<FHierarchicalTreeItemPtr> RootItems; // Contests racines avec hiérarchie
|
||||||
|
TArray<FHierarchicalTreeItemPtr> ParticipantItems; // Participants séparés
|
||||||
|
|
||||||
|
// Widgets - TreeView simple
|
||||||
|
TSharedPtr<STreeView<FHierarchicalTreeItemPtr>> ContestTreeView;
|
||||||
|
TSharedPtr<STreeView<FHierarchicalTreeItemPtr>> ParticipantTreeView;
|
||||||
|
TSharedPtr<STextBlock> StatsText;
|
||||||
|
TSharedPtr<STextBlock> SelectionText;
|
||||||
|
|
||||||
|
// Méthodes de construction
|
||||||
|
void BuildContestHierarchy();
|
||||||
|
void BuildParticipantList();
|
||||||
|
|
||||||
|
// Callbacks TreeView
|
||||||
|
TSharedRef<ITableRow> OnGenerateRowForTree(FHierarchicalTreeItemPtr Item,
|
||||||
|
const TSharedRef<STableViewBase>& OwnerTable);
|
||||||
|
void OnGetChildrenForTree(FHierarchicalTreeItemPtr Item, TArray<FHierarchicalTreeItemPtr>& OutChildren);
|
||||||
|
void OnTreeSelectionChanged(FHierarchicalTreeItemPtr SelectedItem, ESelectInfo::Type SelectInfo);
|
||||||
|
void OnSetExpansionRecursive(FHierarchicalTreeItemPtr Item, bool bIsExpanded);
|
||||||
|
|
||||||
|
// Callbacks des boutons
|
||||||
|
FReply OnRefreshClicked();
|
||||||
|
FReply OnExpandAllClicked();
|
||||||
|
FReply OnCollapseAllClicked();
|
||||||
|
|
||||||
|
EActiveTimerReturnType ForceInitialLayout(double InCurrentTime, float InDeltaTime);
|
||||||
|
|
||||||
|
// Utilitaires
|
||||||
|
FText GetStatsText() const;
|
||||||
|
};
|
||||||
@ -9,7 +9,7 @@ public class DTFluxCore : ModuleRules
|
|||||||
PublicDependencyModuleNames.AddRange(
|
PublicDependencyModuleNames.AddRange(
|
||||||
new string[]
|
new string[]
|
||||||
{
|
{
|
||||||
"Core","JsonUtilities"
|
"Core",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -20,8 +20,8 @@ public class DTFluxCore : ModuleRules
|
|||||||
"Engine",
|
"Engine",
|
||||||
"Slate",
|
"Slate",
|
||||||
"SlateCore",
|
"SlateCore",
|
||||||
"DTFluxProjectSettings",
|
|
||||||
"JsonUtilities",
|
"JsonUtilities",
|
||||||
|
"OutputLog",
|
||||||
"Json",
|
"Json",
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,37 +6,6 @@
|
|||||||
#include "Types/Objects/DTFluxContestStorage.h"
|
#include "Types/Objects/DTFluxContestStorage.h"
|
||||||
|
|
||||||
|
|
||||||
// const FDateTime UDTFluxModelAsset::GetMassStart(const int& ContestId, const int& StageId)
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// void UDTFluxModelAsset::InsertOrUpdateRaceData(const TSharedPtr<FJsonObject>& Response)
|
|
||||||
// {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void UDTFluxModelAsset::InsertOrUpdateContestRanking(const TSharedPtr<FJsonObject>& Response)
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void UDTFluxModelAsset::InsertOrUpdateStageRanking(const TSharedPtr<FJsonObject>& Response)
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void UDTFluxModelAsset::InsertOrUpdateSplitRanking(const TSharedPtr<FJsonObject>& Response)
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void UDTFluxModelAsset::InsertOrUpdateTeamList(const TSharedPtr<FJsonObject>& Response)
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void UDTFluxModelAsset::SplitSensorTriggered(const TSharedPtr<FJsonObject>& Response)
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
UDTFluxModelAsset::UDTFluxModelAsset(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
|
UDTFluxModelAsset::UDTFluxModelAsset(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -55,7 +24,17 @@ bool UDTFluxModelAsset::GetContestById(const int InContestId, FDTFluxContest& Ou
|
|||||||
OutContest = ContestItem.Value;
|
OutContest = ContestItem.Value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxModelAsset::GetStage(FDTFluxStageKey StageKey, FDTFluxStage& OutStage)
|
||||||
|
{
|
||||||
|
FDTFluxContest TargetContest;
|
||||||
|
int TargetStageId = StageKey.StageId;
|
||||||
|
if (GetContestById(StageKey.ContestId, TargetContest))
|
||||||
|
{
|
||||||
|
return TargetContest.GetStage(TargetStageId, OutStage);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -67,18 +46,21 @@ void UDTFluxModelAsset::AddPerson(const FDTFluxPerson& InPerson)
|
|||||||
|
|
||||||
void UDTFluxModelAsset::AddParticipant(const FDTFluxParticipant& InParticipant, const int ContestId)
|
void UDTFluxModelAsset::AddParticipant(const FDTFluxParticipant& InParticipant, const int ContestId)
|
||||||
{
|
{
|
||||||
|
UE_LOG(logDTFluxCore, Error, TEXT("%i Person in Participant %i"), InParticipant.GetTeammateNum(),
|
||||||
|
InParticipant.Bib);
|
||||||
FDTFluxContest TargetContest;
|
FDTFluxContest TargetContest;
|
||||||
if (GetContestById(ContestId, TargetContest))
|
if (GetContestById(ContestId, TargetContest))
|
||||||
{
|
{
|
||||||
if(!PersonExists(InParticipant.Person1))
|
TArray<FDTFluxPerson> Teammate = InParticipant.Teammate;
|
||||||
|
for (auto& Person : InParticipant.Teammate)
|
||||||
{
|
{
|
||||||
AddPerson(InParticipant.Person1);
|
UE_LOG(logDTFluxCore, Error, TEXT("AddParticipant() DTFlux Person %s %s %s"),
|
||||||
}
|
*Person.FirstName, *Person.LastName, *Person.Gender);
|
||||||
if(InParticipant.Person2 != 0)
|
if (!PersonExists(Person))
|
||||||
{
|
{
|
||||||
if(!PersonExists(InParticipant.Person2))
|
UE_LOG(logDTFluxCore, Error, TEXT("AddParticipant() DTFlux Person %s %s %s doesnot exists, adding..."),
|
||||||
{
|
*Person.FirstName, *Person.LastName, *Person.Gender);
|
||||||
AddPerson(InParticipant.Person2);
|
AddPerson(Person);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Participants.Add(InParticipant.Bib, InParticipant);
|
Participants.Add(InParticipant.Bib, InParticipant);
|
||||||
@ -93,3 +75,82 @@ bool UDTFluxModelAsset::PersonExists(const FDTFluxPerson& InPerson) const
|
|||||||
return Person == InPerson;
|
return Person == InPerson;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FString UDTFluxModelAsset::GetContestNameForId(const int InContestID)
|
||||||
|
{
|
||||||
|
FDTFluxContest Contest;
|
||||||
|
if (!GetContestById(InContestID, Contest))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCore, Warning,
|
||||||
|
TEXT("GetContestNameForId(%i) [unable to find a contest] result will be empty !!!"),
|
||||||
|
InContestID);
|
||||||
|
}
|
||||||
|
return Contest.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxModelAsset::AddContestRanking(const FDTFluxContestRankings& NewContestRankings)
|
||||||
|
{
|
||||||
|
ContestRankings.Add(NewContestRankings.ContestId, NewContestRankings);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxModelAsset::UpdateParticipant(const FDTFluxParticipant& Participant)
|
||||||
|
{
|
||||||
|
// TODO : If update is on Bib we are totally lost as we search by bib.
|
||||||
|
int Bib = Participant.Bib;
|
||||||
|
if (Participants.Contains(Bib))
|
||||||
|
{
|
||||||
|
TArray<FDTFluxPerson> InTeammate = Participant.Teammate;
|
||||||
|
Participants[Bib].Elite = Participant.Elite;
|
||||||
|
Participants[Bib].ContestId = Participant.ContestId;
|
||||||
|
Participants[Bib].Club = Participant.Club;
|
||||||
|
Participants[Bib].Category = Participant.Category;
|
||||||
|
Participants[Bib].Team = Participant.Team;
|
||||||
|
Participants[Bib].Status = Participant.Status;
|
||||||
|
//TODO : Update Person
|
||||||
|
for (const auto& Person : InTeammate)
|
||||||
|
{
|
||||||
|
//Don't know what to do...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxModelAsset::UpdateParticipantStatus(const FDTFluxTeamStatusUpdate& NewParticipantStatus)
|
||||||
|
{
|
||||||
|
if (Participants.Contains(NewParticipantStatus.Bib))
|
||||||
|
{
|
||||||
|
Participants[NewParticipantStatus.Bib].Status = NewParticipantStatus.Status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxModelAsset::GetParticipantByBib(int Bib, FDTFluxParticipant& OutParticipant)
|
||||||
|
{
|
||||||
|
if (Participants.Contains(Bib))
|
||||||
|
{
|
||||||
|
OutParticipant = Participants[Bib];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UDTFluxModelAsset::UpdateOrCreateStageRanking(const FDTFluxStageRankings& InStageRankings)
|
||||||
|
{
|
||||||
|
FDTFluxStageKey StageKey = InStageRankings.GetCompositeKey();
|
||||||
|
StageRankings.FindOrAdd(StageKey) = InStageRankings;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxModelAsset::AddStageRanking(const FDTFluxStageRankings& InStageRankings)
|
||||||
|
{
|
||||||
|
StageRankings.Add(InStageRankings.GetCompositeKey(), InStageRankings);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxModelAsset::AddSplitRanking(const FDTFluxSplitRankings& InSplitRanking)
|
||||||
|
{
|
||||||
|
SplitRankings.Add(InSplitRanking.GetCompositeKey(), InSplitRanking);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxModelAsset::UpdateOrCreateSplitRanking(const FDTFluxSplitRankings& InSplitRankings)
|
||||||
|
{
|
||||||
|
FDTFluxSplitKey SplitKey = InSplitRankings.GetCompositeKey();
|
||||||
|
SplitRankings.FindOrAdd(SplitKey) = InSplitRankings;
|
||||||
|
}
|
||||||
|
|||||||
@ -99,17 +99,17 @@ void UDTFluxContestStorage::AddSplit(const int InContest, const FDTFluxSplit& In
|
|||||||
|
|
||||||
#pragma region SplitRankingRegion
|
#pragma region SplitRankingRegion
|
||||||
|
|
||||||
void UDTFluxContestStorage::AddSplitRanking(const int InContest, const FDTFluxSplitRanking& InSplitRanking)
|
void UDTFluxContestStorage::AddSplitRanking(const int InContest, const FDTFluxStageRanking& InSplitRanking)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void UDTFluxContestStorage::GetSplitRankingByParticipant(const FDTFluxParticipant& InParticipant, const int InContestId,
|
void UDTFluxContestStorage::GetSplitRankingByParticipant(const FDTFluxParticipant& InParticipant, const int InContestId,
|
||||||
const int InStageId, const FDTFluxSplitRanking& OutSplitRankingForBib)
|
const int InStageId, const FDTFluxStageRanking& OutSplitRankingForBib)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void UDTFluxContestStorage::GetSplitRankingByBib(const int InBib, const int InContestId, const int InStageId,
|
void UDTFluxContestStorage::GetSplitRankingByBib(const int InBib, const int InContestId, const int InStageId,
|
||||||
const FDTFluxSplitRanking& OutSplitRankingForBib)
|
const FDTFluxStageRanking& OutSplitRankingForBib)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,26 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Types/Objects/UDTFluxParticipantFactory.h"
|
||||||
|
|
||||||
|
bool UDTFluxParticipantFactory::CreateParticipantFomJson(const FString& JsonString, FDTFluxParticipant& OutParticipant)
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> JsonObject;
|
||||||
|
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
|
||||||
|
if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid())
|
||||||
|
{
|
||||||
|
return UDTFluxParticipantFactory::CreateFromJsonCpp(JsonObject, OutParticipant);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OutParticipant = FDTFluxParticipant();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxParticipantFactory::CreateFromJsonCpp(const TSharedPtr<FJsonObject> JsonObject,
|
||||||
|
FDTFluxParticipant& OutParticipant)
|
||||||
|
{
|
||||||
|
OutParticipant = FDTFluxParticipant::CreateFromJson(JsonObject);
|
||||||
|
return !OutParticipant.IsDefault();
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Types/Struct/DTFluxCompositeKey.h"
|
||||||
@ -9,21 +9,3 @@ void FDTFluxContestRanking::Dump() const
|
|||||||
TEXT("FDTFluxContestRanking ->> \n \"rank\" : %d, Participant with Bib %d \"Gap\" : %s, \"Time\" : %s "),
|
TEXT("FDTFluxContestRanking ->> \n \"rank\" : %d, Participant with Bib %d \"Gap\" : %s, \"Time\" : %s "),
|
||||||
Rank, Bib, *Gap, *Time);
|
Rank, Bib, *Gap, *Time);
|
||||||
};
|
};
|
||||||
|
|
||||||
void FDTFluxStageRanking::Dump() const
|
|
||||||
{
|
|
||||||
UE_LOG(logDTFluxCore, Log, TEXT("RANKING : %02d. Participant bib %d %s %s %s %s %s"),
|
|
||||||
Rank, Bib, *Gap, *TimeSwim,
|
|
||||||
*TimeTransition, *TimeRun, *StartTime.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void FDTFluxSplitRanking::Dump() const
|
|
||||||
{
|
|
||||||
UE_LOG(logDTFluxCore, Log, TEXT("SplitGapItem"))
|
|
||||||
// Participant.Dump();
|
|
||||||
UE_LOG(logDTFluxCore, Log, TEXT("Bib %02d Rank %02d Gap %s Time %s"), Bib, Rank, *Gap, *Time);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Types/Struct/DTFluxSplitSensor.h"
|
||||||
@ -3,105 +3,144 @@
|
|||||||
|
|
||||||
#include "Types/Struct/DTFluxTeamListStruct.h"
|
#include "Types/Struct/DTFluxTeamListStruct.h"
|
||||||
|
|
||||||
|
#include "DTFluxCoreModule.h"
|
||||||
|
|
||||||
bool FDTFluxParticipant::IsTeam() const
|
|
||||||
|
void FDTFluxParticipant::AddTeammate(const FDTFluxPerson& Person)
|
||||||
{
|
{
|
||||||
return Person2.FirstName.IsEmpty() && Person2.LastName.IsEmpty();
|
Teammate.Add(Person);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FDTFluxParticipant::AddTeammate(const FString LastName, const FString FirstName, const FString Gender)
|
||||||
void FDTFluxParticipant::Dump() const
|
|
||||||
{
|
{
|
||||||
FString EliteStr = "NO";
|
|
||||||
if(Elite)
|
|
||||||
{
|
|
||||||
EliteStr = "YES";
|
|
||||||
}
|
}
|
||||||
UE_LOG(logDTFluxCore, Log, TEXT("PARTICIPANT with bib: %03d"), Bib);
|
|
||||||
UE_LOG(logDTFluxCore, Log, TEXT("Fullname : %s %s"), *Person1.FirstName, *Person1.LastName);
|
FString FDTFluxParticipant::GetFormattedName(const int MaxChar, const FString OverflowChars) const
|
||||||
|
{
|
||||||
|
{
|
||||||
|
if (MaxChar <= 0)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
FString FirstName;
|
||||||
|
FString LastName;
|
||||||
if (IsTeam())
|
if (IsTeam())
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxCore, Log, TEXT("Teamate : %s %s"), *Person2.FirstName, *Person2.LastName);
|
LastName = Team;
|
||||||
UE_LOG(logDTFluxCore, Log, TEXT("Team name : %s"), *Team);
|
|
||||||
}
|
|
||||||
UE_LOG(logDTFluxCore, Log, TEXT("Club : %s, Category : %s, IsElite : %s, Status : %s"),
|
|
||||||
*Club, *Category, *EliteStr, *UEnum::GetValueAsString(Status));
|
|
||||||
}
|
|
||||||
|
|
||||||
FString FDTFluxParticipant::GetParticipantFormatedName(bool Truncate, int MaxSize) const
|
|
||||||
{
|
|
||||||
FString ParticipantName;
|
|
||||||
if(Truncate)
|
|
||||||
{
|
|
||||||
if(IsTeam())
|
|
||||||
{
|
|
||||||
//Concatenate the team name;
|
|
||||||
if(Team.Len() > MaxSize - 3)
|
|
||||||
{
|
|
||||||
return Team.Left(MaxSize - 3).Append(TEXT("..."));
|
|
||||||
}
|
|
||||||
return Team;
|
|
||||||
}
|
|
||||||
if(Person1.FirstName.Contains("-") )
|
|
||||||
{
|
|
||||||
FString Formated = "";
|
|
||||||
//Compound Firstname
|
|
||||||
TArray<FString> Out;
|
|
||||||
Person1.FirstName.ParseIntoArray(Out,TEXT("-"),true);
|
|
||||||
for(const auto& Str : Out)
|
|
||||||
{
|
|
||||||
Formated.Append(Str.Left(1).ToUpper()).Append(".");
|
|
||||||
}
|
|
||||||
// TODO : Camel Case handling for LastName
|
|
||||||
Formated.Append(" ").Append(*Person1.LastName);
|
|
||||||
UE_LOG(logDTFluxCore, Log, TEXT("Firstname is with space compound. Formated Name %s length %02d MAX Size : %02d"),
|
|
||||||
*Formated, Formated.Len(), MaxSize);
|
|
||||||
if(Formated.Len() >= MaxSize)
|
|
||||||
{
|
|
||||||
UE_LOG(logDTFluxCore, Log, TEXT("Reducing %s Formated"), *Formated);
|
|
||||||
|
|
||||||
return Formated.Left(MaxSize - 3).Append("...");
|
|
||||||
}
|
|
||||||
return Formated;
|
|
||||||
}
|
|
||||||
if(Person1.FirstName.Contains(" "))
|
|
||||||
{
|
|
||||||
FString Formated = "";
|
|
||||||
//Compound Firstname
|
|
||||||
TArray<FString> Out;
|
|
||||||
Person1.FirstName.ParseIntoArray(Out,TEXT(" "),true);
|
|
||||||
for(const auto& Str : Out)
|
|
||||||
{
|
|
||||||
Formated.Append(Str.Left(1).ToUpper()).Append(".");
|
|
||||||
}
|
|
||||||
// TODO : Camel Case handling for LastName
|
|
||||||
Formated.Append(" ").Append(*Person1.LastName);
|
|
||||||
UE_LOG(logDTFluxCore, Log, TEXT("Firstname is with space compound. Formated Name %s length %02d MAX Size : %02d"),
|
|
||||||
*Formated, Formated.Len(), MaxSize);
|
|
||||||
if(Formated.Len() >= MaxSize)
|
|
||||||
{
|
|
||||||
UE_LOG(logDTFluxCore, Log, TEXT("Reducing %s Formated"), *Formated);
|
|
||||||
return Formated.Left(MaxSize - 3).Append("...");
|
|
||||||
}
|
|
||||||
return Formated;
|
|
||||||
}
|
|
||||||
FString Formated = Person1.FirstName.Left(1).Append(". ");
|
|
||||||
Formated.Append(Person1.LastName);
|
|
||||||
UE_LOG(logDTFluxCore, Log, TEXT("Firstname is not compound. Formated Name %s length %02d MAX Size : %02d"),
|
|
||||||
*Formated, Formated.Len(), MaxSize);
|
|
||||||
if(Formated.Len() >= MaxSize)
|
|
||||||
{
|
|
||||||
UE_LOG(logDTFluxCore, Log, TEXT("Reducing %s Formated"), *Formated);
|
|
||||||
return Formated.Left(MaxSize - 3).Append("...");
|
|
||||||
}
|
|
||||||
return Formated;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(!IsTeam())
|
FirstName = Teammate[0].FirstName;
|
||||||
|
LastName = Teammate[0].LastName;
|
||||||
|
}
|
||||||
|
FString Initial;
|
||||||
|
if (!FirstName.IsEmpty())
|
||||||
{
|
{
|
||||||
return FString::Printf(TEXT("%s %s"), *Person1.FirstName, *Person2.LastName);
|
Initial = FirstName.Left(1).ToUpper() + " ";
|
||||||
}
|
}
|
||||||
return Team;
|
|
||||||
|
FString FormattedLastName = LastName.ToUpper();
|
||||||
|
|
||||||
|
FString FullName = Initial + FormattedLastName;
|
||||||
|
UE_LOG(logDTFluxCore, Error, TEXT("FullName for Bib %i is %s"), Bib, *FullName);
|
||||||
|
|
||||||
|
if (FullName.Len() <= MaxChar)
|
||||||
|
{
|
||||||
|
return FullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 OverflowLength = OverflowChars.Len();
|
||||||
|
|
||||||
|
if (OverflowLength > MaxChar)
|
||||||
|
{
|
||||||
|
return FullName.Left(MaxChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Initial.Len() + OverflowLength > MaxChar)
|
||||||
|
{
|
||||||
|
return FullName.Left(MaxChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 AvailableForLastName = MaxChar - Initial.Len() - OverflowLength;
|
||||||
|
|
||||||
|
if (AvailableForLastName <= 0)
|
||||||
|
{
|
||||||
|
return FullName.Left(MaxChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
FString TruncatedName = Initial + FormattedLastName.Left(AvailableForLastName) + OverflowChars;
|
||||||
|
|
||||||
|
if (TruncatedName.Len() > MaxChar)
|
||||||
|
{
|
||||||
|
return TruncatedName.Left(MaxChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TruncatedName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FString FDTFluxParticipant::GetConcatFormattedName(const int MaxChar, const FString OverflowChar) const
|
||||||
|
{
|
||||||
|
FString BibText = FString::FromInt(Bib) + " ";
|
||||||
|
FString FormattedName = GetFormattedName(MaxChar - BibText.Len(), OverflowChar);
|
||||||
|
return BibText + FormattedName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructeur privé depuis JSON
|
||||||
|
FDTFluxParticipant::FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject)
|
||||||
|
: Bib(JsonObject->GetIntegerField(TEXT("bib")))
|
||||||
|
, ContestId(JsonObject->GetIntegerField(TEXT("contestId")))
|
||||||
|
, Category(JsonObject->GetStringField(TEXT("category")))
|
||||||
|
, Club(JsonObject->GetStringField(TEXT("club")))
|
||||||
|
, Elite(JsonObject->GetBoolField(TEXT("elite")))
|
||||||
|
, Status(static_cast<EDTFluxParticipantStatusType>(JsonObject->GetIntegerField(TEXT("status"))))
|
||||||
|
, Team(JsonObject->GetStringField(TEXT("team")))
|
||||||
|
, bIsMassStartParticipant(false)
|
||||||
|
, CurrentSplit(-1)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCore, Error, TEXT("Ctor with JSON Object"))
|
||||||
|
for (uint8 Index = 1; ; Index++)
|
||||||
|
{
|
||||||
|
FString FirstNameKey = Index == 1 ? "firstName" : FString::Printf(TEXT("firstName%i"), Index);
|
||||||
|
FString LastNameKey = Index == 1 ? "lastName" : FString::Printf(TEXT("lastName%i"), Index);
|
||||||
|
FString GenderKey = Index == 1 ? "gender" : FString::Printf(TEXT("gender%i"), Index);
|
||||||
|
// max 10 Persons
|
||||||
|
if (Index >= 10)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!JsonObject->HasField(FirstNameKey) && !JsonObject->HasField(LastNameKey)
|
||||||
|
&& !JsonObject->HasField(GenderKey))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCore, Error, TEXT("No Corresponding Field!!!"))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const FString FirstName = JsonObject->GetStringField(FirstNameKey);
|
||||||
|
const FString LastName = JsonObject->GetStringField(LastNameKey);
|
||||||
|
const FString Gender = JsonObject->GetStringField(GenderKey);
|
||||||
|
if (FirstName.IsEmpty() && LastName.IsEmpty())
|
||||||
|
continue;
|
||||||
|
FDTFluxPerson Person;
|
||||||
|
Person.FirstName = FirstName;
|
||||||
|
Person.LastName = LastName;
|
||||||
|
Person.Gender = Gender;
|
||||||
|
Teammate.Add(Person);
|
||||||
|
}
|
||||||
|
UE_LOG(logDTFluxCore, Error, TEXT("Ctor with JSON Object Teammate is now %i long"), Teammate.Num());
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxParticipant FDTFluxParticipant::CreateFromJson(const TSharedPtr<FJsonObject>& JsonObject)
|
||||||
|
{
|
||||||
|
return FDTFluxParticipant(JsonObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
int FDTFluxParticipant::GetTeammateNum() const
|
||||||
|
{
|
||||||
|
return Teammate.Num();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxParticipant::IsTeam() const
|
||||||
|
{
|
||||||
|
return Teammate.Num() < 1;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
// Fill out your copyright notice in the Description page of Project Settings.
|
|
||||||
|
|
||||||
|
|
||||||
#include "Types/Struct/FDTFluxPoursuiteStruct.h"
|
|
||||||
|
|
||||||
FText FDTFluxPoursuite::GetParticipantFormatedName() const
|
|
||||||
{
|
|
||||||
return FText();
|
|
||||||
}
|
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Types/Struct/FDTFluxPursuitInfo.h"
|
||||||
@ -5,7 +5,9 @@
|
|||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "UObject/Object.h"
|
#include "UObject/Object.h"
|
||||||
#include "Dom/JsonObject.h"
|
#include "Dom/JsonObject.h"
|
||||||
|
#include "Types/Struct/DTFluxCompositeKey.h"
|
||||||
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
||||||
|
#include "Types/Struct/DTFluxSplitSensor.h"
|
||||||
#include "DTFluxModelAsset.generated.h"
|
#include "DTFluxModelAsset.generated.h"
|
||||||
|
|
||||||
|
|
||||||
@ -18,8 +20,8 @@ class DTFLUXCORE_API UDTFluxModelAsset : public UObject
|
|||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
UDTFluxModelAsset(const FObjectInitializer& ObjectInitializer);
|
UDTFluxModelAsset(const FObjectInitializer& ObjectInitializer);
|
||||||
public:
|
|
||||||
|
|
||||||
|
public:
|
||||||
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||||
FString EventName = "MyEvent";
|
FString EventName = "MyEvent";
|
||||||
|
|
||||||
@ -34,10 +36,13 @@ public:
|
|||||||
TMap<FString /* ContestName */, FDTFluxContest> Contests;
|
TMap<FString /* ContestName */, FDTFluxContest> Contests;
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
TArray<FDTFluxContestRanking> ContestRankings;
|
TMap<int /*ContestId*/, FDTFluxContestRankings> ContestRankings;
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
TMap<FString /*ContestName*/ ,FDTFluxStageRanking> StageRankings;
|
TMap<FDTFluxStageKey, FDTFluxStageRankings> StageRankings;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
|
TMap<FDTFluxSplitKey, FDTFluxSplitRankings> SplitRankings;
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|ModelAsset")
|
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|ModelAsset")
|
||||||
void AddContest(const FDTFluxContest& Contest);
|
void AddContest(const FDTFluxContest& Contest);
|
||||||
@ -45,12 +50,43 @@ public:
|
|||||||
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Contest")
|
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Contest")
|
||||||
bool GetContestById(const int InContestId, FDTFluxContest& OutContest);
|
bool GetContestById(const int InContestId, FDTFluxContest& OutContest);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Contest")
|
||||||
|
bool GetStage(FDTFluxStageKey StageKey, FDTFluxStage& OutStage);
|
||||||
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Person")
|
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Person")
|
||||||
void AddPerson(const FDTFluxPerson& InPerson);
|
void AddPerson(const FDTFluxPerson& InPerson);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Participant")
|
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Participant")
|
||||||
void AddParticipant(const FDTFluxParticipant& InParticipant, const int ContestId);
|
void AddParticipant(const FDTFluxParticipant& InParticipant, const int ContestId);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Person")
|
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Person|Utils")
|
||||||
bool PersonExists(const FDTFluxPerson& InPerson) const;
|
bool PersonExists(const FDTFluxPerson& InPerson) const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Contest|Utils")
|
||||||
|
FString GetContestNameForId(const int InContestID);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Stage")
|
||||||
|
void UpdateOrCreateStageRanking(const FDTFluxStageRankings& InStageRankings);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Stage")
|
||||||
|
void AddStageRanking(const FDTFluxStageRankings& InStageRankings);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Split")
|
||||||
|
void UpdateOrCreateSplitRanking(const FDTFluxSplitRankings& InSplitRankings);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Split")
|
||||||
|
void AddSplitRanking(const FDTFluxSplitRankings& InSplitRanking);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void AddContestRanking(const FDTFluxContestRankings& NewContestRankings);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Participant")
|
||||||
|
void UpdateParticipant(const FDTFluxParticipant& Participant);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Participant")
|
||||||
|
void UpdateParticipantStatus(const FDTFluxTeamStatusUpdate& NewParticipantStatus);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Participant")
|
||||||
|
bool GetParticipantByBib(int Bib, FDTFluxParticipant& OutParticipant);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,24 +3,72 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
|
#include "DTFluxCoreEnum.generated.h"
|
||||||
|
|
||||||
|
|
||||||
UENUM(BlueprintType)
|
UENUM(BlueprintType)
|
||||||
enum class EDTFluxRequestType : uint8
|
enum class EDTFluxApiDataType : uint8
|
||||||
{
|
{
|
||||||
None = 0 UMETA(DisplayName="None"),
|
None = 0 UMETA(DisplayName="None"),
|
||||||
|
|
||||||
|
// Types bidirectionnels (requête/réponse)
|
||||||
ContestRanking = 1 UMETA(DisplayName="contest-ranking"),
|
ContestRanking = 1 UMETA(DisplayName="contest-ranking"),
|
||||||
StageRanking = 2 UMETA(DisplayName="stage-ranking"),
|
StageRanking = 2 UMETA(DisplayName="stage-ranking"),
|
||||||
SplitRanking = 3 UMETA(DisplayName="split-ranking"),
|
SplitRanking = 3 UMETA(DisplayName="split-ranking"),
|
||||||
TeamList = 4 UMETA(DisplayName="team-list"),
|
TeamList = 4 UMETA(DisplayName="team-list"),
|
||||||
RaceData = 5 UMETA(DisplayName="race-data"),
|
RaceData = 5 UMETA(DisplayName="race-data"),
|
||||||
|
|
||||||
|
// Types uniquement réponse (push du serveur)
|
||||||
|
TeamUpdate = 10 UMETA(DisplayName="team-update"),
|
||||||
|
StatusUpdate = 11 UMETA(DisplayName="status-update"),
|
||||||
|
SplitSensor = 12 UMETA(DisplayName="split-sensor"),
|
||||||
|
|
||||||
|
// Types système
|
||||||
|
Error = 99 UMETA(DisplayName="error"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Alias pour clarifier l'usage
|
||||||
|
using EDTFluxRequestType = EDTFluxApiDataType;
|
||||||
|
using EDTFluxResponseType = EDTFluxApiDataType;
|
||||||
|
|
||||||
|
// Utilitaires pour valider les usages
|
||||||
|
namespace DTFluxDataTypeUtils
|
||||||
|
{
|
||||||
|
inline bool CanBeRequested(EDTFluxApiDataType Type)
|
||||||
|
{
|
||||||
|
return static_cast<uint8>(Type) >= 1 && static_cast<uint8>(Type) <= 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsPushOnly(EDTFluxApiDataType Type)
|
||||||
|
{
|
||||||
|
return static_cast<uint8>(Type) >= 10 && static_cast<uint8>(Type) <= 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsValidResponseType(EDTFluxApiDataType Type)
|
||||||
|
{
|
||||||
|
return Type != EDTFluxApiDataType::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// UENUM(BlueprintType)
|
||||||
|
// enum class EDTFluxRequestType : uint8
|
||||||
|
// {
|
||||||
|
// None = 0 UMETA(DisplayName="None"),
|
||||||
|
// ContestRanking = 1 UMETA(DisplayName="contest-ranking"),
|
||||||
|
// StageRanking = 2 UMETA(DisplayName="stage-ranking"),
|
||||||
|
// SplitRanking = 3 UMETA(DisplayName="split-ranking"),
|
||||||
|
// TeamList = 4 UMETA(DisplayName="team-list"),
|
||||||
|
// RaceData = 5 UMETA(DisplayName="race-data"),
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
UENUM(BlueprintType)
|
UENUM(BlueprintType)
|
||||||
enum class EDTFluxConnectionStatus : uint8
|
enum class EDTFluxConnectionStatus : uint8
|
||||||
{
|
{
|
||||||
Unset = 0 UMETA(DisplayName="Unset"),
|
Unset = 0 UMETA(DisplayName="Unset"),
|
||||||
Connected = 1 << 0 UMETA(DisplayName="Connected"),
|
Connected = 1 << 0 UMETA(DisplayName="Connected"),
|
||||||
Error = 2 << 1 UMETA(DisplayName="Error")
|
Error = 1 << 1 UMETA(DisplayName="Error"),
|
||||||
|
Closed = 1 << 2 UMETA(DisplayName="Closed"),
|
||||||
|
NotConnected = 1 << 3 UMETA(DisplayName="NotConnected")
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,6 +17,7 @@ enum class EDTFluxParticipantStatusType : uint8
|
|||||||
DNF = 3 UMETA(DisplayName="Abandon"),
|
DNF = 3 UMETA(DisplayName="Abandon"),
|
||||||
DNS = 4 UMETA(DisplayName="NonPartant"),
|
DNS = 4 UMETA(DisplayName="NonPartant"),
|
||||||
NotLinedUp = 5 UMETA(DisplayName="NonPresentAuDépart"),
|
NotLinedUp = 5 UMETA(DisplayName="NonPresentAuDépart"),
|
||||||
|
Unknown = 1 << 4 UMETA(DisplayName="Unknown")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ ENUM_CLASS_FLAGS(EDTFluxSplitType);
|
|||||||
|
|
||||||
|
|
||||||
UENUM(BlueprintType, meta=(Bitflags, UseEnumValuesAsMaskValuesInEditor=true))
|
UENUM(BlueprintType, meta=(Bitflags, UseEnumValuesAsMaskValuesInEditor=true))
|
||||||
enum EDTFluxSortingFilter : uint8
|
enum class EDTFluxSortingFilter : uint8
|
||||||
{
|
{
|
||||||
None = 0b00000000 UMETA(DisplayName="No Sorting"),
|
None = 0b00000000 UMETA(DisplayName="No Sorting"),
|
||||||
IgnoreStatusOut = 0b00000001 UMETA(DisplayName="IgnoreStatusOut"),
|
IgnoreStatusOut = 0b00000001 UMETA(DisplayName="IgnoreStatusOut"),
|
||||||
@ -69,3 +70,20 @@ enum EDTFluxSortingFilter : uint8
|
|||||||
DescendingByRank= Descending | ByRank UMETA(DisplayName="DescendingByRank")
|
DescendingByRank= Descending | ByRank UMETA(DisplayName="DescendingByRank")
|
||||||
};
|
};
|
||||||
ENUM_CLASS_FLAGS(EDTFluxSortingFilter);
|
ENUM_CLASS_FLAGS(EDTFluxSortingFilter);
|
||||||
|
|
||||||
|
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EDTFluxSortingRankingType: uint8
|
||||||
|
{
|
||||||
|
Rank = 0b00000000 UMETA(DisplayName="Rank (Default)"),
|
||||||
|
Name = 0b00000001 UMETA(DisplayName="Name"),
|
||||||
|
Bib = 0b00000010 UMETA(DisplayName="Bib"),
|
||||||
|
TimeSwim = 0b00000100 UMETA(DisplayName="Swimming Time"),
|
||||||
|
TimeTransition = 0b00001000 UMETA(DisplayName="Transition Time"),
|
||||||
|
TimeRun = TimeSwim|TimeTransition UMETA(DisplayName="Running Time"),
|
||||||
|
StartTime = 0b00001110 UMETA(DisplayName="StartTime"),
|
||||||
|
Gap = 0b00010000 UMETA(DisplayName="StartTime"),
|
||||||
|
SwimSpeed = 0b00100000 UMETA(DisplayName="StartTime"),
|
||||||
|
RunningSpeed = 0b01000000 UMETA(DisplayName="StartTime"),
|
||||||
|
TotalSpeed = 0b10000000 UMETA(DisplayName="StartTime"),
|
||||||
|
};
|
||||||
@ -88,13 +88,13 @@ public:
|
|||||||
void AddSplit(const int InContest, const FDTFluxSplit& InSplit);
|
void AddSplit(const int InContest, const FDTFluxSplit& InSplit);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Contest")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Contest")
|
||||||
void AddSplitRanking(const int InContest, const FDTFluxSplitRanking& InSplitRanking);
|
void AddSplitRanking(const int InContest, const FDTFluxStageRanking& InSplitRanking);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Contest")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Contest")
|
||||||
void GetSplitRankingByParticipant(const FDTFluxParticipant& InParticipant, const int InContestId, const int InStageId, const FDTFluxSplitRanking& OutSplitRankingForBib);
|
void GetSplitRankingByParticipant(const FDTFluxParticipant& InParticipant, const int InContestId, const int InStageId, const FDTFluxStageRanking& OutSplitRankingForBib);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Contest")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Contest")
|
||||||
void GetSplitRankingByBib(const int InBib, const int InContestId, const int InStageId, const FDTFluxSplitRanking& OutSplitRankingForBib);
|
void GetSplitRankingByBib(const int InBib, const int InContestId, const int InStageId, const FDTFluxStageRanking& OutSplitRankingForBib);
|
||||||
|
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
#pragma region ParticipantSection
|
#pragma region ParticipantSection
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||||
|
#include "Types/Struct/DTFluxTeamListStruct.h"
|
||||||
|
#include "UDTFluxParticipantFactory.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class DTFLUXCORE_API UDTFluxParticipantFactory : public UBlueprintFunctionLibrary
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Factory")
|
||||||
|
static bool CreateParticipantFomJson(const FString& JsonString, FDTFluxParticipant& OutParticipant );
|
||||||
|
|
||||||
|
static bool CreateFromJsonCpp(const TSharedPtr<FJsonObject> JsonObject, FDTFluxParticipant& OutParticipant);
|
||||||
|
};
|
||||||
111
Source/DTFluxCore/Public/Types/Struct/DTFluxCompositeKey.h
Normal file
111
Source/DTFluxCore/Public/Types/Struct/DTFluxCompositeKey.h
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "DTFluxCompositeKey.generated.h"
|
||||||
|
|
||||||
|
USTRUCT()
|
||||||
|
struct FDTFluxCompositeKey
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct DTFLUXCORE_API FDTFluxStageKey : public FDTFluxCompositeKey
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
FDTFluxStageKey() = default;
|
||||||
|
|
||||||
|
FDTFluxStageKey(const int InContestId, const int InStageId)
|
||||||
|
: ContestId(InContestId)
|
||||||
|
, StageId(InStageId)
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||||
|
int ContestId = -1;
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||||
|
int StageId = -1;
|
||||||
|
|
||||||
|
friend uint32 GetTypeHash(const FDTFluxStageKey& Key)
|
||||||
|
{
|
||||||
|
return HashCombine(
|
||||||
|
GetTypeHash(Key.ContestId),
|
||||||
|
GetTypeHash(Key.StageId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const FDTFluxStageKey& Other) const
|
||||||
|
{
|
||||||
|
return ContestId == Other.ContestId && StageId == Other.StageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString GetDisplayName() const
|
||||||
|
{
|
||||||
|
return FString::Printf(TEXT("Contest%i -| Stage%i"), ContestId, StageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
FText GetTooltipText() const
|
||||||
|
{
|
||||||
|
return FText::Format(INVTEXT("Contest{0}|Stage{1}"),
|
||||||
|
FText::AsNumber(ContestId),
|
||||||
|
FText::AsNumber(StageId));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct DTFLUXCORE_API FDTFluxSplitKey : public FDTFluxCompositeKey
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
FDTFluxSplitKey() = default;
|
||||||
|
|
||||||
|
FDTFluxSplitKey(const int InContestId, const int InStageId, const int InSplitId)
|
||||||
|
: ContestId(InContestId)
|
||||||
|
, StageId(InStageId)
|
||||||
|
, SplitId(InSplitId)
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||||
|
int ContestId = -1;
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||||
|
int StageId = -1;
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||||
|
int SplitId = -1;
|
||||||
|
|
||||||
|
friend uint32 GetTypeHash(const FDTFluxSplitKey& Key)
|
||||||
|
{
|
||||||
|
return HashCombine(
|
||||||
|
GetTypeHash(Key.ContestId),
|
||||||
|
GetTypeHash(Key.StageId),
|
||||||
|
GetTypeHash(Key.SplitId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const FDTFluxSplitKey& Other) const
|
||||||
|
{
|
||||||
|
return ContestId == Other.ContestId && StageId == Other.StageId && SplitId == Other.SplitId;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString GetDisplayName() const
|
||||||
|
{
|
||||||
|
return FString::Printf(TEXT("Contest%i | Stage%i | Split%i"), ContestId, StageId, SplitId);
|
||||||
|
}
|
||||||
|
|
||||||
|
FText GetTooltipText() const
|
||||||
|
{
|
||||||
|
return FText::Format(INVTEXT("Contest{0}|Stage{1}|Split{2}"),
|
||||||
|
FText::AsNumber(ContestId),
|
||||||
|
FText::AsNumber(StageId),
|
||||||
|
FText::AsNumber(SplitId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -23,7 +23,7 @@ public:
|
|||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
||||||
int Bib = -1;
|
int Bib = -1;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
||||||
FDTFluxSplitRanking SplitRanking;
|
FDTFluxStageRanking SplitRanking;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
||||||
FDTFluxStageRanking StageRanking;
|
FDTFluxStageRanking StageRanking;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,18 +17,18 @@ USTRUCT(BlueprintType, Category="DTFlux|RaceData")
|
|||||||
struct DTFLUXCORE_API FDTFluxSplit
|
struct DTFLUXCORE_API FDTFluxSplit
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
||||||
int SplitId = -1;
|
int SplitId = -1;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
||||||
FString Name;
|
FString Name;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||||
TArray<FDTFluxSplitRanking> SplitRankings;
|
TArray<FDTFluxStageRanking> SplitRankings;
|
||||||
// void Dump() const;
|
// void Dump() const;
|
||||||
// // void InsertOrReplace(const FDTFluxStageRankingResponseItem& SplitRankingItemResp);
|
// // void InsertOrReplace(const FDTFluxStageRankingResponseItem& SplitRankingItemResp);
|
||||||
// void SortByRank();
|
// void SortByRank();
|
||||||
// TArray<FDTFluxSplitRanking> GetSplitRanking(const int From = 0, const int DisplayNumber = 0);
|
// TArray<FDTFluxSplitRanking> GetSplitRanking(const int From = 0, const int DisplayNumber = 0);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,6 +39,7 @@ USTRUCT(BlueprintType, Category="DTFlux|RaceData")
|
|||||||
struct DTFLUXCORE_API FDTFluxStage
|
struct DTFLUXCORE_API FDTFluxStage
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||||
int StageId;
|
int StageId;
|
||||||
@ -60,6 +61,7 @@ USTRUCT(BlueprintType, Category="DTFlux|RaceData")
|
|||||||
struct DTFLUXCORE_API FDTFluxContest
|
struct DTFLUXCORE_API FDTFluxContest
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||||
int ContestId = -1;
|
int ContestId = -1;
|
||||||
@ -73,18 +75,89 @@ public:
|
|||||||
TArray<FDTFluxSplit> Splits;
|
TArray<FDTFluxSplit> Splits;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||||
FDateTime Date;
|
FDateTime Date;
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||||
|
FDateTime EndTime;
|
||||||
|
UPROPERTY()
|
||||||
|
int LastStageId = -1;
|
||||||
|
|
||||||
|
|
||||||
|
bool IsFinished() const;
|
||||||
|
inline void UpdateEndTime();
|
||||||
|
int GetLastStageId();
|
||||||
|
void UpdateLastStageId();
|
||||||
|
FDTFluxStage& GetLastStage() const;
|
||||||
|
bool GetStage(const int StageID, FDTFluxStage& OutStage) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline bool FDTFluxContest::IsFinished() const
|
||||||
|
{
|
||||||
|
return EndTime <= FDateTime::Now();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void FDTFluxContest::UpdateEndTime()
|
||||||
|
{
|
||||||
|
TArray<FDTFluxStage> TempStages = Stages;
|
||||||
|
TempStages.Sort([](const FDTFluxStage& A, const FDTFluxStage& B)
|
||||||
|
{
|
||||||
|
return A.EndTime < B.EndTime;
|
||||||
|
});
|
||||||
|
EndTime = TempStages.Last().EndTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int FDTFluxContest::GetLastStageId()
|
||||||
|
{
|
||||||
|
if (LastStageId <= 0)
|
||||||
|
{
|
||||||
|
UpdateLastStageId();
|
||||||
|
}
|
||||||
|
return LastStageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void FDTFluxContest::UpdateLastStageId()
|
||||||
|
{
|
||||||
|
TArray<FDTFluxStage> TempStages = Stages;
|
||||||
|
TempStages.Sort([](const FDTFluxStage& A, const FDTFluxStage& B)
|
||||||
|
{
|
||||||
|
return A.StageId < B.StageId;
|
||||||
|
});
|
||||||
|
LastStageId = TempStages.Last().StageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline FDTFluxStage& FDTFluxContest::GetLastStage() const
|
||||||
|
{
|
||||||
|
TArray<FDTFluxStage> TempStages = Stages;
|
||||||
|
TempStages.Sort([](const FDTFluxStage& A, const FDTFluxStage& B)
|
||||||
|
{
|
||||||
|
return A.StageId < B.StageId;
|
||||||
|
});
|
||||||
|
return TempStages.Last();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool FDTFluxContest::GetStage(const int StageID, FDTFluxStage& OutStage) const
|
||||||
|
{
|
||||||
|
if (Stages.Num() == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const FDTFluxStage& Stage : Stages)
|
||||||
|
{
|
||||||
|
if (Stage.StageId == StageID)
|
||||||
|
{
|
||||||
|
OutStage = Stage;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
USTRUCT()
|
USTRUCT()
|
||||||
struct DTFLUXCORE_API FDTFluxRaceData
|
struct DTFLUXCORE_API FDTFluxRaceData
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
public:
|
|
||||||
|
|
||||||
|
public:
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
// ReSharper disable once IdentifierTypo
|
// ReSharper disable once IdentifierTypo
|
||||||
TArray<FDTFluxContest> Datas;
|
TArray<FDTFluxContest> Datas;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
|
#include "DTFluxCompositeKey.h"
|
||||||
#include "UObject/Object.h"
|
#include "UObject/Object.h"
|
||||||
#include "DTFluxRankingStructs.generated.h"
|
#include "DTFluxRankingStructs.generated.h"
|
||||||
|
|
||||||
@ -15,88 +16,188 @@ USTRUCT(BlueprintType, Category="DTFlux|Model")
|
|||||||
struct DTFLUXCORE_API FDTFluxContestRanking
|
struct DTFLUXCORE_API FDTFluxContestRanking
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
int Bib;
|
int Bib;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
int Rank;
|
int Rank;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
FString Gap;
|
FString Gap;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
FString Time;
|
FString Time;
|
||||||
UPROPERTY();
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere);
|
||||||
FString SpeedSwimAverage;
|
FString SpeedSwimAverage;
|
||||||
UPROPERTY();
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere);
|
||||||
FString SpeedRunningAverage;
|
FString SpeedRunningAverage;
|
||||||
UPROPERTY();
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere);
|
||||||
FString SpeedTotalAverage;
|
FString SpeedTotalAverage;
|
||||||
void Dump() const;
|
void Dump() const;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FDTFluxContestRankings
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||||
|
TArray<FDTFluxContestRanking> Rankings;
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||||
|
int ContestId;
|
||||||
|
//TODO check if necessary ???
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||||
|
FString ContestName;
|
||||||
|
|
||||||
|
void SetName(const FString Name)
|
||||||
|
{
|
||||||
|
ContestName = Name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @struct FDTFluxStageRanking
|
* @struct FDTFluxStageRanking
|
||||||
* Representing a stage ranking for a participant
|
* Representing a stage ranking for a participant
|
||||||
*/
|
*/
|
||||||
USTRUCT(BlueprintType, Category="DTFlux|Model")
|
USTRUCT(BlueprintType, Category="DTFlux|Model")
|
||||||
struct DTFLUXCORE_API FDTFluxStageRanking
|
struct DTFLUXCORE_API FDTFluxDetailedRankingItem
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
public:
|
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
|
||||||
int Bib;
|
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
|
||||||
int Rank;
|
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
|
||||||
FString Gap;
|
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
|
||||||
FString Time;
|
|
||||||
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;
|
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
|
||||||
FDateTime StartTime;
|
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
|
||||||
float SpeedRunning;
|
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
|
||||||
float SpeedTotal;
|
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
|
||||||
float SpeedSwim;
|
|
||||||
void Dump() const;
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
|
int Bib;
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
|
int Rank;
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
|
FString Gap;
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
|
FString Time;
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
|
FString TimeSwim;
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
|
FString TimeTransition;
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
|
FString TimeRun;
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
|
FString TimeStart;
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
|
FDateTime StartTime;
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
|
FString SpeedRunning;
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
|
FString SpeedTotal;
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
|
FString SpeedSwim;
|
||||||
|
void Dump() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FDTFluxDetailedRankings
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||||
|
int ContestId = -1;
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||||
|
int StageId = -1;
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||||
|
TArray<FDTFluxDetailedRankingItem> Rankings;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @struct FDTFluxSplitRanking
|
* @struct FDTFluxStageRanking
|
||||||
* Representing a ranking of a participant in a split
|
* Representing a stage ranking for a participant
|
||||||
|
* This struct is only a cosmetic Struct
|
||||||
*/
|
*/
|
||||||
USTRUCT(BlueprintType, Category="DTFlux|Model")
|
USTRUCT(BlueprintType)
|
||||||
struct DTFLUXCORE_API FDTFluxSplitRanking
|
struct FDTFluxStageRanking : public FDTFluxDetailedRankingItem
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
public:
|
};
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
|
||||||
int Bib;
|
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
/**
|
||||||
int ContestId = 0;
|
* @struct FDTFluxSplitRanking
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
* Representing a split ranking for a participant
|
||||||
int StageId = 0;
|
* This struct is only a cosmetic Struct
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
*/
|
||||||
int SplitId = 0;
|
USTRUCT(BlueprintType, Category="DTFlux|Model")
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
struct DTFLUXCORE_API FDTFluxSplitRanking : public FDTFluxStageRanking
|
||||||
FString Gap;
|
{
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
GENERATED_BODY()
|
||||||
FString Time;
|
};
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
|
||||||
int Rank = 0;
|
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
/**
|
||||||
bool Display = false;
|
* @struct FDTFluxStageRankings
|
||||||
void Dump() const;
|
* StageRanking Container Struct
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FDTFluxStageRankings : public FDTFluxDetailedRankings
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
inline FDTFluxStageKey GetKeyFrom(const FDTFluxStageRankings& InRankings)
|
||||||
|
{
|
||||||
|
return FDTFluxStageKey(InRankings.ContestId, InRankings.StageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline FDTFluxStageKey GetCompositeKey() const
|
||||||
|
{
|
||||||
|
return FDTFluxStageKey(ContestId, StageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsInitialized() const
|
||||||
|
{
|
||||||
|
return ContestId > 0 && StageId > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Initialize()
|
||||||
|
{
|
||||||
|
for (auto& Ranking : Rankings)
|
||||||
|
{
|
||||||
|
FDateTime RankingStartTime;
|
||||||
|
if (Ranking.TimeStart != "")
|
||||||
|
{
|
||||||
|
TArray<FString> Exploded;
|
||||||
|
Ranking.TimeStart.ParseIntoArray(Exploded, TEXT(":"), true);
|
||||||
|
if (Exploded.Num() == 3)
|
||||||
|
{
|
||||||
|
FDateTime Now = FDateTime::Now();
|
||||||
|
RankingStartTime = FDateTime(Now.GetYear(), Now.GetMonth(), Now.GetDay(),
|
||||||
|
FCString::Atoi(*Exploded[0]), FCString::Atoi(*Exploded[1]),
|
||||||
|
FCString::Atoi(*Exploded[2]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ranking.StartTime = RankingStartTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FDTFluxSplitRankings : public FDTFluxDetailedRankings
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||||
|
int SplitId;
|
||||||
|
|
||||||
|
inline static FDTFluxSplitKey GetKeyFrom(const FDTFluxSplitRankings& InRankings)
|
||||||
|
{
|
||||||
|
return FDTFluxSplitKey(InRankings.ContestId, InRankings.StageId, InRankings.SplitId);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline FDTFluxSplitKey GetCompositeKey() const
|
||||||
|
{
|
||||||
|
return FDTFluxSplitKey(ContestId, StageId, SplitId);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsInitialized() const
|
||||||
|
{
|
||||||
|
return ContestId > 0 && StageId > 0 && SplitId > 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
37
Source/DTFluxCore/Public/Types/Struct/DTFluxSplitSensor.h
Normal file
37
Source/DTFluxCore/Public/Types/Struct/DTFluxSplitSensor.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "DTFluxTeamListStruct.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "DTFluxSplitSensor.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FDTFluxSplitSensorInfo
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
FDTFluxSplitSensorInfo() = default;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
|
int Bib = -1;
|
||||||
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
|
int ContestId = -1;
|
||||||
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
|
int StageId = -1;
|
||||||
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
|
int SplitId = -1;
|
||||||
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
|
FString Time = "";
|
||||||
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
|
FString Gap = "-";
|
||||||
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
|
int Rank;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "UObject/Object.h"
|
#include "UObject/Object.h"
|
||||||
#include "DTFluxCoreModule.h"
|
|
||||||
#include "Types/Enum/DTFluxModelEnums.h"
|
#include "Types/Enum/DTFluxModelEnums.h"
|
||||||
#include "DTFluxTeamListStruct.generated.h"
|
#include "DTFluxTeamListStruct.generated.h"
|
||||||
|
|
||||||
@ -12,6 +11,7 @@ USTRUCT()
|
|||||||
struct DTFLUXCORE_API FDTFluxTeamListItemDefinition
|
struct DTFLUXCORE_API FDTFluxTeamListItemDefinition
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Type = "team-list-item";
|
FString Type = "team-list-item";
|
||||||
@ -43,27 +43,12 @@ public:
|
|||||||
FString Club;
|
FString Club;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @struct FDTFluxTeamListDefinition
|
|
||||||
* Struct representing the Participant List definition
|
|
||||||
* Used to exchange data between Objects in the system
|
|
||||||
*/
|
|
||||||
USTRUCT(BlueprintType)
|
|
||||||
struct DTFLUXCORE_API FDTFluxTeamListDefinition
|
|
||||||
{
|
|
||||||
GENERATED_BODY()
|
|
||||||
public:
|
|
||||||
UPROPERTY()
|
|
||||||
// ReSharper disable once IdentifierTypo
|
|
||||||
TArray<FDTFluxTeamListItemDefinition> Datas;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
USTRUCT(BlueprintType, Category="DTFlux|Model")
|
USTRUCT(BlueprintType, Category="DTFlux|Model")
|
||||||
struct DTFLUXCORE_API FDTFluxPerson
|
struct DTFLUXCORE_API FDTFluxPerson
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||||
FString FirstName;
|
FString FirstName;
|
||||||
@ -81,14 +66,17 @@ public:
|
|||||||
return FirstName.ToLower() + LastName.ToLower() + Gender.ToLower()
|
return FirstName.ToLower() + LastName.ToLower() + Gender.ToLower()
|
||||||
== Right.FirstName.ToLower() + Right.LastName.ToLower() + Right.Gender.ToLower();
|
== Right.FirstName.ToLower() + Right.LastName.ToLower() + Right.Gender.ToLower();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const int Length) const
|
bool operator==(const int Length) const
|
||||||
{
|
{
|
||||||
return (FirstName.ToLower() + LastName.ToLower() + Gender.ToLower()).Len() == Length;
|
return (FirstName.ToLower() + LastName.ToLower() + Gender.ToLower()).Len() == Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator!=(const int Length) const
|
bool operator!=(const int Length) const
|
||||||
{
|
{
|
||||||
return !(*this == Length);
|
return !(*this == Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator!=(const FDTFluxPerson& Right) const
|
bool operator!=(const FDTFluxPerson& Right) const
|
||||||
{
|
{
|
||||||
return FirstName.ToLower() + LastName.ToLower() + Gender.ToLower()
|
return FirstName.ToLower() + LastName.ToLower() + Gender.ToLower()
|
||||||
@ -101,14 +89,48 @@ USTRUCT(BlueprintType, Category="DTFlux|Model")
|
|||||||
struct DTFLUXCORE_API FDTFluxParticipant
|
struct DTFLUXCORE_API FDTFluxParticipant
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
friend class UDTFluxModelAsset;
|
||||||
|
friend class UDTFluxParticipantFactory;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
// Constructeur public par défaut requis par Unreal
|
||||||
|
FDTFluxParticipant()
|
||||||
|
: Bib(-1)
|
||||||
|
, ContestId(-1)
|
||||||
|
, Elite(false)
|
||||||
|
, Status(static_cast<EDTFluxParticipantStatusType>(0))
|
||||||
|
, bIsMassStartParticipant(false)
|
||||||
|
, CurrentSplit(-1)
|
||||||
|
{
|
||||||
|
Teammate.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si le participant est dans son état par défaut (non initialisé)
|
||||||
|
* @return True si tous les champs sont à leur valeur par défaut
|
||||||
|
*/
|
||||||
|
bool IsDefault() const
|
||||||
|
{
|
||||||
|
return Bib == -1
|
||||||
|
&& ContestId == -1
|
||||||
|
&& Category.IsEmpty()
|
||||||
|
&& Club.IsEmpty()
|
||||||
|
&& !Elite
|
||||||
|
&& Status == static_cast<EDTFluxParticipantStatusType>(0)
|
||||||
|
&& Team.IsEmpty()
|
||||||
|
&& !bIsMassStartParticipant
|
||||||
|
&& CurrentSplit == -1
|
||||||
|
&& Teammate.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|model", EditAnywhere)
|
||||||
int Bib = -1;
|
int Bib = -1;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|model", EditAnywhere)
|
||||||
FDTFluxPerson Person1;
|
int ContestId = -1;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|model", EditAnywhere)
|
||||||
FDTFluxPerson Person2;
|
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
|
||||||
FString Category;
|
FString Category;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||||
FString Club;
|
FString Club;
|
||||||
@ -121,9 +143,96 @@ public:
|
|||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||||
bool bIsMassStartParticipant = false;
|
bool bIsMassStartParticipant = false;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
|
||||||
int LastSplitId = 0;
|
int CurrentSplit = -1;
|
||||||
bool IsTeam() const;
|
|
||||||
void Dump() const;
|
|
||||||
FString GetParticipantFormatedName(bool Truncate = false, int MaxSize = 20) const;
|
|
||||||
|
|
||||||
|
// void Dump() const;
|
||||||
|
void AddTeammate(const FDTFluxPerson& Person);
|
||||||
|
void AddTeammate(const FString LastName, const FString FirstName, const FString Gender);
|
||||||
|
|
||||||
|
FText GetFormattedNameText(const int MaxChar = 15, const FString OverflowChar = FString("...")) const
|
||||||
|
{
|
||||||
|
return FText::FromString(GetFormattedName(MaxChar, OverflowChar));
|
||||||
|
};
|
||||||
|
|
||||||
|
FText GetConcatFormattedNameText(const int MaxChar = 20, const FString OverflowChar = FString("...")) const
|
||||||
|
{
|
||||||
|
return FText::FromString(GetConcatFormattedName(MaxChar, OverflowChar));
|
||||||
|
};
|
||||||
|
FString GetFormattedName(const int MaxChar = 15, const FString OverflowChar = FString("...")) const;
|
||||||
|
FString GetConcatFormattedName(const int MaxChar = 20, const FString OverflowChar = FString("...")) const;
|
||||||
|
|
||||||
|
static FString GetFormattedName(const FDTFluxParticipant& Participant, const int MaxChar = 15,
|
||||||
|
const FString OverflowChar = FString("..."))
|
||||||
|
{
|
||||||
|
return Participant.GetFormattedName(MaxChar, OverflowChar);
|
||||||
|
};
|
||||||
|
|
||||||
|
static FString GetConcatFormattedName(const FDTFluxParticipant& Participant, const int MaxChar = 15,
|
||||||
|
const FString OverflowChar = FString("..."))
|
||||||
|
{
|
||||||
|
return Participant.GetConcatFormattedName(MaxChar, OverflowChar);
|
||||||
|
};
|
||||||
|
|
||||||
|
static FText GetFormattedNameText(const FDTFluxParticipant& Participant, const int MaxChar = 15,
|
||||||
|
const FString OverflowChar = FString("..."))
|
||||||
|
{
|
||||||
|
return Participant.GetFormattedNameText();
|
||||||
|
};
|
||||||
|
|
||||||
|
static FText GetConcatFormattedNameText(const FDTFluxParticipant& Participant, const int MaxChar = 15,
|
||||||
|
const FString OverflowChar = FString("..."))
|
||||||
|
{
|
||||||
|
return Participant.GetConcatFormattedNameText();
|
||||||
|
};
|
||||||
|
const TArray<FDTFluxPerson> GetTeammate() const { return Teammate; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// --- Constructeur privé ---
|
||||||
|
explicit FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
UPROPERTY(Category="DTFlux|model", VisibleAnywhere)
|
||||||
|
TArray<FDTFluxPerson> Teammate;
|
||||||
|
// Méthode publique pour construire à partir d'un JSON (utilisée par la factory)
|
||||||
|
static FDTFluxParticipant CreateFromJson(const TSharedPtr<FJsonObject>& JsonObject);
|
||||||
|
int GetTeammateNum() const;
|
||||||
|
bool IsTeam() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct FDTFluxTeamListDefinition
|
||||||
|
* Struct representing the Participant List definition
|
||||||
|
* Used to exchange data between Objects in the system
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct DTFLUXCORE_API FDTFluxTeamListDefinition
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY()
|
||||||
|
// ReSharper disable once IdentifierTypo
|
||||||
|
TArray<FDTFluxParticipant> Participants;
|
||||||
|
};
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FDTFluxTeamStatusUpdate
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
FDTFluxTeamStatusUpdate() = default;
|
||||||
|
|
||||||
|
FDTFluxTeamStatusUpdate(const int InBib, const int InStatus)
|
||||||
|
: Bib(InBib)
|
||||||
|
, Status(static_cast<EDTFluxParticipantStatusType>(InStatus))
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Participant")
|
||||||
|
int Bib = -1;
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Participant")
|
||||||
|
EDTFluxParticipantStatusType Status = EDTFluxParticipantStatusType::Unknown;
|
||||||
};
|
};
|
||||||
@ -1,27 +0,0 @@
|
|||||||
// Fill out your copyright notice in the Description page of Project Settings.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
|
||||||
#include "DTFluxTeamListStruct.h"
|
|
||||||
#include "UObject/Object.h"
|
|
||||||
#include "FDTFluxPoursuiteStruct.generated.h"
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @struct FDTFluxPoursuite
|
|
||||||
* Representing a
|
|
||||||
*/
|
|
||||||
USTRUCT(BlueprintType, Category="DTFlux|Poursuite")
|
|
||||||
struct FDTFluxPoursuite
|
|
||||||
{
|
|
||||||
GENERATED_BODY()
|
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Poursuite")
|
|
||||||
FDTFluxParticipant Participant;
|
|
||||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Poursuite")
|
|
||||||
FDateTime TimeStart;
|
|
||||||
FText GetParticipantFormatedName() const;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
38
Source/DTFluxCore/Public/Types/Struct/FDTFluxPursuitInfo.h
Normal file
38
Source/DTFluxCore/Public/Types/Struct/FDTFluxPursuitInfo.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "DTFluxTeamListStruct.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "FDTFluxPursuitInfo.generated.h"
|
||||||
|
|
||||||
|
USTRUCT(Blueprintable, BlueprintType)
|
||||||
|
struct FDTFluxPursuitInfo
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
FDTFluxPursuitInfo() = default;
|
||||||
|
|
||||||
|
FDTFluxPursuitInfo(int InBib, FDateTime InStartTime, bool InbIsMassStart = false) :
|
||||||
|
bIsMassStart(InbIsMassStart),
|
||||||
|
Bib(InBib),
|
||||||
|
StartTime(InStartTime)
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
//TODO : Set this property to BlueprintReadOnly
|
||||||
|
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||||
|
bool bIsMassStart = false;
|
||||||
|
|
||||||
|
//TODO : Set this property to BlueprintReadOnly
|
||||||
|
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||||
|
int Bib = -1;
|
||||||
|
|
||||||
|
//TODO : Set this property to BlueprintReadOnly
|
||||||
|
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||||
|
FDateTime StartTime;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
int ContestId = -1;
|
||||||
|
};
|
||||||
@ -20,6 +20,7 @@ public class DTFluxCoreSubsystem : ModuleRules
|
|||||||
"Engine",
|
"Engine",
|
||||||
"Slate",
|
"Slate",
|
||||||
"SlateCore",
|
"SlateCore",
|
||||||
|
"UnrealEd",
|
||||||
"DTFluxNetwork",
|
"DTFluxNetwork",
|
||||||
"DTFluxProjectSettings",
|
"DTFluxProjectSettings",
|
||||||
"DTFluxCore",
|
"DTFluxCore",
|
||||||
|
|||||||
@ -6,8 +6,11 @@
|
|||||||
|
|
||||||
#include "DTFluxCoreSubsystemModule.h"
|
#include "DTFluxCoreSubsystemModule.h"
|
||||||
#include "DTFluxGeneralSettings.h"
|
#include "DTFluxGeneralSettings.h"
|
||||||
|
#include "DTFluxPursuitManager.h"
|
||||||
|
#include "FileHelpers.h"
|
||||||
#include "Assets/DTFluxModelAsset.h"
|
#include "Assets/DTFluxModelAsset.h"
|
||||||
#include "Subsystems/DTFluxNetworkSubsystem.h"
|
#include "Subsystems/DTFluxNetworkSubsystem.h"
|
||||||
|
#include "UObject/SavePackage.h"
|
||||||
|
|
||||||
void UDTFluxCoreSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
void UDTFluxCoreSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||||
{
|
{
|
||||||
@ -18,21 +21,18 @@ void UDTFluxCoreSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
|||||||
const UDTFluxGeneralSettings* GeneralSettings = GetDefault<UDTFluxGeneralSettings>();
|
const UDTFluxGeneralSettings* GeneralSettings = GetDefault<UDTFluxGeneralSettings>();
|
||||||
TSoftObjectPtr<UDTFluxModelAsset> ModelAsset = GeneralSettings->ModelAsset;
|
TSoftObjectPtr<UDTFluxModelAsset> ModelAsset = GeneralSettings->ModelAsset;
|
||||||
DataStorage = ModelAsset.LoadSynchronous();
|
DataStorage = ModelAsset.LoadSynchronous();
|
||||||
// UE_LOG(logDTFluxCore, Log, TEXT("GeneralSettings is nullptr -> %s"), GeneralSettings == nullptr ? TEXT("TRUE") : TEXT("FALSE"));
|
if (!DataStorage)
|
||||||
// UE_LOG(logDTFluxCore, Log, TEXT("ModelAsset isNull() -> %s"), ModelAsset.IsNull() ? TEXT("TRUE") : TEXT("FALSE"));
|
{
|
||||||
// UE_LOG(logDTFluxCore, Log, TEXT("ModelAsset IsValid() -> %s"), ModelAsset.IsValid() ? TEXT("TRUE") : TEXT("FALSE"));
|
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage Not Valid"));
|
||||||
// UE_LOG(logDTFluxCore, Log, TEXT("DataStorage is nullptr -> %s"), DataStorage == nullptr ? TEXT("TRUE") : TEXT("FALSE"));
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO REMOVE This as it's only for testing purpose
|
//TODO REMOVE This as it's only for testing purpose
|
||||||
NetworkSubsystem = GEngine->GetEngineSubsystem<UFDTFluxNetworkSubsystem>();
|
NetworkSubsystem = GEngine->GetEngineSubsystem<UDTFluxNetworkSubsystem>();
|
||||||
if (NetworkSubsystem->WsStatus != EDTFluxConnectionStatus::Connected)
|
if (NetworkSubsystem->WsStatus != EDTFluxConnectionStatus::Connected)
|
||||||
{
|
{
|
||||||
RegisterDelegates();
|
RegisterDelegates();
|
||||||
NetworkSubsystem->Connect();
|
|
||||||
}
|
}
|
||||||
|
PursuitManager = NewObject<UDTFluxPursuitManager>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UDTFluxCoreSubsystem::Deinitialize()
|
void UDTFluxCoreSubsystem::Deinitialize()
|
||||||
@ -40,22 +40,76 @@ void UDTFluxCoreSubsystem::Deinitialize()
|
|||||||
Super::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::RegisterDelegates()
|
void UDTFluxCoreSubsystem::RegisterDelegates()
|
||||||
{
|
{
|
||||||
if (NetworkSubsystem)
|
if (NetworkSubsystem)
|
||||||
{
|
{
|
||||||
NetworkSubsystem->OnReceivedRaceData().AddDynamic(this, &UDTFluxCoreSubsystem::ParseRaceData);
|
// ✅ Binding avec vérification automatique des signatures
|
||||||
NetworkSubsystem->OnReceivedTeamList().AddDynamic(this, &UDTFluxCoreSubsystem::ParseTeamList);
|
// 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
|
||||||
|
);
|
||||||
|
|
||||||
|
// ⚠️ ATTENTION : Vous avez un doublon ici !
|
||||||
|
// NetworkSubsystem->OnReceivedTeamUpdate().BindUFunction(this, "ProcessTeamList");
|
||||||
|
|
||||||
|
NetworkSubsystem->OnReceivedTeamStatusUpdate().BindUObject(
|
||||||
|
this,
|
||||||
|
&UDTFluxCoreSubsystem::ProcessTeamStatusUpdate
|
||||||
|
);
|
||||||
|
|
||||||
|
NetworkSubsystem->OnReceivedTeamUpdate().BindUObject(
|
||||||
|
this,
|
||||||
|
&UDTFluxCoreSubsystem::ProcessTeamUpdate
|
||||||
|
);
|
||||||
|
|
||||||
|
NetworkSubsystem->OnReceivedSplitSensor().BindUObject(
|
||||||
|
this,
|
||||||
|
&UDTFluxCoreSubsystem::ProcessSplitSensor
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UDTFluxCoreSubsystem::ProcessRaceData(const FDTFluxRaceData& RaceDataDefinition)
|
||||||
void UDTFluxCoreSubsystem::ParseRaceData(const FDTFluxRaceData& RaceDataDefinition)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
if (RaceDataDefinition.Datas.Num() > 0)
|
if (RaceDataDefinition.Datas.Num() > 0)
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Receiving RaceDataDefinition [%s]"), *RaceDataDefinition.Datas[0].Name);
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Receiving RaceDataDefinition [%s]"),
|
||||||
|
*RaceDataDefinition.Datas[0].Name);
|
||||||
if (DataStorage != nullptr)
|
if (DataStorage != nullptr)
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage Name %s"), *DataStorage->EventName);
|
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage Name %s"), *DataStorage->EventName);
|
||||||
@ -68,41 +122,81 @@ void UDTFluxCoreSubsystem::ParseRaceData(const FDTFluxRaceData& RaceDataDefiniti
|
|||||||
{
|
{
|
||||||
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage is null"));
|
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage is null"));
|
||||||
}
|
}
|
||||||
|
SaveDataStorage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("RaceDataDefinition is empty !!!"));
|
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("RaceDataDefinition is empty !!!"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UDTFluxCoreSubsystem::ParseTeamList(const FDTFluxTeamListDefinition& TeamListDefinition)
|
void UDTFluxCoreSubsystem::ProcessTeamList(const FDTFluxTeamListDefinition& TeamListDefinition)
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received TeamList with %i Items"), TeamListDefinition.Datas.Num());
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received TeamList with %i Items"),
|
||||||
for(auto InParticipantDefinition : TeamListDefinition.Datas)
|
TeamListDefinition.Participants.Num());
|
||||||
|
for (const auto& Participant : TeamListDefinition.Participants)
|
||||||
{
|
{
|
||||||
FDTFluxParticipant NewParticipant;
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Add Participant %i in %i ContestId"),
|
||||||
NewParticipant.Person1.Gender = InParticipantDefinition.Gender;
|
Participant.Bib, Participant.ContestId);
|
||||||
NewParticipant.Person1.FirstName = InParticipantDefinition.FirstName;
|
|
||||||
NewParticipant.Person1.LastName = InParticipantDefinition.LastName;
|
DataStorage->AddParticipant(Participant, Participant.ContestId);
|
||||||
if(InParticipantDefinition.Team != "")
|
|
||||||
{
|
|
||||||
NewParticipant.Person2.Gender = InParticipantDefinition.Gender2;
|
|
||||||
NewParticipant.Person2.FirstName = InParticipantDefinition.FirstName2;
|
|
||||||
NewParticipant.Person2.LastName = InParticipantDefinition.LastName2;
|
|
||||||
}
|
}
|
||||||
NewParticipant.Bib = InParticipantDefinition.Bib;
|
SaveDataStorage();
|
||||||
NewParticipant.Category = InParticipantDefinition.Category;
|
}
|
||||||
NewParticipant.Club = InParticipantDefinition.Club;
|
|
||||||
NewParticipant.Status = static_cast<EDTFluxParticipantStatusType>(InParticipantDefinition.Status);
|
void UDTFluxCoreSubsystem::ProcessContestRanking(const FDTFluxContestRankings& ContestRankings)
|
||||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Add Participant %s %s in %i ContestId"),
|
{
|
||||||
*NewParticipant.Person1.FirstName, *NewParticipant.Person1.LastName, InParticipantDefinition.ContestId );
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received ContestRankings with %i Items"),
|
||||||
DataStorage->AddParticipant(NewParticipant, InParticipantDefinition.ContestId);
|
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);
|
||||||
|
SaveDataStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxCoreSubsystem::ProcessStageRanking(const FDTFluxStageRankings& StageRankings)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received StageRankings with %i Items"), StageRankings.Rankings.Num());
|
||||||
|
DataStorage->UpdateOrCreateStageRanking(StageRankings);
|
||||||
|
SaveDataStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxCoreSubsystem::ProcessSplitRanking(const FDTFluxSplitRankings& SplitRankings)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received SplitRanking with %i Items"), SplitRankings.Rankings.Num());
|
||||||
|
DataStorage->UpdateOrCreateSplitRanking(SplitRankings);
|
||||||
|
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::OnDataReceived()
|
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)
|
void UDTFluxCoreSubsystem::SendRequest(const FString& Message)
|
||||||
{
|
{
|
||||||
if (NetworkSubsystem)
|
if (NetworkSubsystem)
|
||||||
@ -111,7 +205,6 @@ void UDTFluxCoreSubsystem::SendRequest(const FString& Message)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void UDTFluxCoreSubsystem::SendTeamListRequest()
|
void UDTFluxCoreSubsystem::SendTeamListRequest()
|
||||||
{
|
{
|
||||||
if (NetworkSubsystem)
|
if (NetworkSubsystem)
|
||||||
@ -157,12 +250,119 @@ void UDTFluxCoreSubsystem::RequestAllSplitRankingOfContest(int InContestId, int
|
|||||||
// TODO Implement this
|
// TODO Implement this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FDTFluxStageRankings UDTFluxCoreSubsystem::GetStageRankings(FDTFluxStageKey StageKey)
|
||||||
|
{
|
||||||
|
if (DataStorage->StageRankings.Contains(StageKey))
|
||||||
|
{
|
||||||
|
return DataStorage->StageRankings[StageKey];
|
||||||
|
}
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Cannot find StageRankings for key [%s]"), *StageKey.GetDisplayName());
|
||||||
|
return FDTFluxStageRankings();
|
||||||
|
}
|
||||||
|
|
||||||
void UDTFluxCoreSubsystem::RequestAllSplitRankingOfStage(int InContestId, int InStageId, int InSplitId)
|
void UDTFluxCoreSubsystem::RequestAllSplitRankingOfStage(int InContestId, int InStageId, int InSplitId)
|
||||||
{
|
{
|
||||||
// TODO Implement this
|
// TODO Implement this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FDTFluxParticipant UDTFluxCoreSubsystem::GetParticipant(int InBib)
|
||||||
|
{
|
||||||
|
if (DataStorage->Participants.Contains(InBib))
|
||||||
|
{
|
||||||
|
return DataStorage->Participants[InBib];
|
||||||
|
}
|
||||||
|
return FDTFluxParticipant();
|
||||||
|
}
|
||||||
|
|
||||||
void UDTFluxCoreSubsystem::RefreshStorage()
|
void UDTFluxCoreSubsystem::RefreshStorage()
|
||||||
{
|
{
|
||||||
// TODO Implement this
|
// TODO Implement this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TArray<int> UDTFluxCoreSubsystem::GetCurrentContestsId()
|
||||||
|
{
|
||||||
|
return GetContestsIdForTime(FDateTime::Now());
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FDTFluxContest> UDTFluxCoreSubsystem::GetCurrentContests()
|
||||||
|
{
|
||||||
|
return GetContestsForTime(FDateTime::Now());
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<int> UDTFluxCoreSubsystem::GetContestsIdForTime(const FDateTime Time)
|
||||||
|
{
|
||||||
|
TArray<int> 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<FDTFluxContest> UDTFluxCoreSubsystem::GetContestsForTime(const FDateTime Time)
|
||||||
|
{
|
||||||
|
TArray<FDTFluxContest> 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<FDTFluxStage> RequestedStages) const
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FDTFluxContest> UDTFluxCoreSubsystem::GetContests()
|
||||||
|
{
|
||||||
|
if (DataStorage)
|
||||||
|
{
|
||||||
|
TArray<FDTFluxContest> OutContests;
|
||||||
|
DataStorage->Contests.GenerateValueArray(OutContests);
|
||||||
|
return OutContests;
|
||||||
|
}
|
||||||
|
return TArray<FDTFluxContest>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxCoreSubsystem::LaunchPursuitSequenceFor(const TArray<int> ContestIds)
|
||||||
|
{
|
||||||
|
TArray<FDTFluxContest> Contests = TArray<FDTFluxContest>();
|
||||||
|
for (const auto& ContestId : ContestIds)
|
||||||
|
{
|
||||||
|
FDTFluxContest Contest;
|
||||||
|
GetContestForId(ContestId, Contest);
|
||||||
|
Contests.Add(Contest);
|
||||||
|
if (PursuitManager)
|
||||||
|
{
|
||||||
|
PursuitManager->LaunchPursuitSequenceFor(Contests);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("PursuitManager is null"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
103
Source/DTFluxCoreSubsystem/Private/DTFluxCoreSubsystemTools.cpp
Normal file
103
Source/DTFluxCoreSubsystem/Private/DTFluxCoreSubsystemTools.cpp
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "DTFluxCoreSubsystemTools.h"
|
||||||
|
|
||||||
|
void UDTFluxCoreSubsystemTools::FilterContestRankings(FDTFluxContestRankings& ContestRankings,
|
||||||
|
const EDTFluxSortingRankingType RankingType, TArray<FDTFluxContestRanking>& OutContestRankings, bool bAscendant)
|
||||||
|
{
|
||||||
|
// On fait une copie locale des Rankings
|
||||||
|
TArray<FDTFluxContestRanking> ContestArray = ContestRankings.Rankings;
|
||||||
|
|
||||||
|
// Tri par type + direction
|
||||||
|
ContestArray.Sort([RankingType, bAscendant](const FDTFluxContestRanking& A, const FDTFluxContestRanking& B)
|
||||||
|
{
|
||||||
|
switch (RankingType)
|
||||||
|
{
|
||||||
|
case EDTFluxSortingRankingType::Rank:
|
||||||
|
return bAscendant ? A.Rank < B.Rank : A.Rank > B.Rank;
|
||||||
|
|
||||||
|
case EDTFluxSortingRankingType::Bib:
|
||||||
|
return bAscendant ? A.Bib < B.Bib : A.Bib > B.Bib;
|
||||||
|
|
||||||
|
case EDTFluxSortingRankingType::Gap:
|
||||||
|
return CompareTimeString(A.Gap, B.Gap, bAscendant);
|
||||||
|
|
||||||
|
case EDTFluxSortingRankingType::SwimSpeed:
|
||||||
|
return CompareSpeed(A.SpeedSwimAverage, B.SpeedSwimAverage, bAscendant);
|
||||||
|
|
||||||
|
case EDTFluxSortingRankingType::RunningSpeed:
|
||||||
|
return CompareSpeed(A.SpeedRunningAverage, B.SpeedRunningAverage, bAscendant);
|
||||||
|
|
||||||
|
case EDTFluxSortingRankingType::TotalSpeed:
|
||||||
|
return CompareSpeed(A.SpeedTotalAverage, B.SpeedTotalAverage, bAscendant);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return CompareTimeString(A.Time, B.Time, bAscendant);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Réaffecte les données triées
|
||||||
|
ContestRankings.Rankings = ContestArray;
|
||||||
|
OutContestRankings = ContestArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxCoreSubsystemTools::FilterStageRankings(FDTFluxStageRankings& InStageRankings,
|
||||||
|
const EDTFluxSortingRankingType RankingType, FDTFluxStageRankings& OutStageRankings, bool bAscendant)
|
||||||
|
{
|
||||||
|
// TArray<FDTFluxDetailedRankings> StageArray = static_cast<TDF>()InStageRankings.Rankings;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxCoreSubsystemTools::FilterSplitRankings(FDTFluxSplitRankings& SplitRankings,
|
||||||
|
const EDTFluxSortingRankingType RankinType, FDTFluxSplitRankings& OutSplitRankings, bool bAscendant)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float UDTFluxCoreSubsystemTools::ConvertTimeStringToSeconds(const FString& TimeString)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Format attendu : "HH:MM:SS"
|
||||||
|
TArray<FString> Parts;
|
||||||
|
TimeString.ParseIntoArray(Parts, TEXT(":"), true);
|
||||||
|
|
||||||
|
if (Parts.Num() == 3)
|
||||||
|
{
|
||||||
|
const int32 Hours = FCString::Atoi(*Parts[0]);
|
||||||
|
const int32 Minutes = FCString::Atoi(*Parts[1]);
|
||||||
|
const int32 Seconds = FCString::Atoi(*Parts[2]);
|
||||||
|
return Hours * 3600 + Minutes * 60 + Seconds;
|
||||||
|
}
|
||||||
|
if (Parts.Num() == 2)
|
||||||
|
{
|
||||||
|
const int32 Minutes = FCString::Atoi(*Parts[0]);
|
||||||
|
const int32 Seconds = FCString::Atoi(*Parts[1]);
|
||||||
|
return 3600 + Minutes * 60 + Seconds;
|
||||||
|
}
|
||||||
|
if (Parts.Num() == 1)
|
||||||
|
{
|
||||||
|
return FCString::Atoi(*Parts[0]);
|
||||||
|
}
|
||||||
|
return -1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxCoreSubsystemTools::CompareTimeString(const FString& A_TimeStr, const FString& B_TimeStr, bool bAscendant)
|
||||||
|
{
|
||||||
|
const float A_Time = ConvertTimeStringToSeconds(A_TimeStr);
|
||||||
|
const float B_Time = ConvertTimeStringToSeconds(B_TimeStr);
|
||||||
|
|
||||||
|
return bAscendant ? A_Time < B_Time : A_Time > B_Time;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxCoreSubsystemTools::CompareSpeed(const FString& A_SpeedStr, const FString& B_SpeedStr, bool bAscendant)
|
||||||
|
{
|
||||||
|
float A_Speed = FCString::Atof(*A_SpeedStr);
|
||||||
|
float B_Speed = FCString::Atof(*B_SpeedStr);
|
||||||
|
|
||||||
|
if (bAscendant)
|
||||||
|
{
|
||||||
|
return A_Speed < B_Speed;
|
||||||
|
}
|
||||||
|
return A_Speed > B_Speed;
|
||||||
|
}
|
||||||
|
|
||||||
152
Source/DTFluxCoreSubsystem/Private/DTFluxPursuitManager.cpp
Normal file
152
Source/DTFluxCoreSubsystem/Private/DTFluxPursuitManager.cpp
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "DTFluxPursuitManager.h"
|
||||||
|
|
||||||
|
#include "DTFluxCoreSubsystemModule.h"
|
||||||
|
|
||||||
|
UDTFluxPursuitManager::UDTFluxPursuitManager(const FObjectInitializer& ObjectInitializer):
|
||||||
|
Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO : Add way to pass MaxSimultaneousPursuit and MassStartDelay
|
||||||
|
// For now it's done in UPROPERTIES
|
||||||
|
void UDTFluxPursuitManager::LaunchPursuitSequenceFor(const TArray<FDTFluxContest> InContests)
|
||||||
|
{
|
||||||
|
if (InitSubSystems())
|
||||||
|
{
|
||||||
|
for (const auto Contest : InContests)
|
||||||
|
{
|
||||||
|
FRequestData RequestData;
|
||||||
|
RequestData.ContestId = Contest.ContestId;
|
||||||
|
uint8 StageId = Contest.Stages.Last().StageId;
|
||||||
|
FGuid Guid = NetworkSubsystem->SendTrackedRequestWithCallback(EDTFluxApiDataType::StageRanking,
|
||||||
|
Contest.ContestId, StageId, -1,
|
||||||
|
FOnDTFluxTrackedRequestResponse::CreateUObject(
|
||||||
|
this,
|
||||||
|
&UDTFluxPursuitManager::OnRequestResponse),
|
||||||
|
FOnDTFluxTrackedRequestTimeout::CreateUObject(
|
||||||
|
this,
|
||||||
|
&UDTFluxPursuitManager::OnRequestTimeoutResponse),
|
||||||
|
FOnDTFluxRequestResponseError::CreateUObject(
|
||||||
|
this,
|
||||||
|
&UDTFluxPursuitManager::OnRequestError));
|
||||||
|
|
||||||
|
RequestData.RequestIds.Add(Guid);
|
||||||
|
PendingRequestData.Add(RequestData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxPursuitManager::OnRequestResponse(const FGuid& RequestId, FDTFluxServerResponse& Response)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Log,
|
||||||
|
TEXT("UDTFluxPursuitManager::OnRequestResponse() Received Ranking For Stage %i"), Response.StageID)
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("Response is %s"), *UEnum::GetValueAsString(Response.GetResponseType()))
|
||||||
|
//check if request
|
||||||
|
if (Response.GetResponseType() == EDTFluxApiDataType::StageRanking)
|
||||||
|
{
|
||||||
|
FDTFluxStageRankings Rankings;
|
||||||
|
FRequestData FoundData;
|
||||||
|
if (Response.ParseStageRankingResponse(Rankings))
|
||||||
|
{
|
||||||
|
for (auto& PendingReq : PendingRequestData)
|
||||||
|
{
|
||||||
|
// Check for a matching PendingReq
|
||||||
|
if (PendingReq.IsWaitingFor(RequestId, Rankings))
|
||||||
|
{
|
||||||
|
FoundData = PendingReq;
|
||||||
|
// A request Is Terminated
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Log,
|
||||||
|
TEXT("UDTFluxPursuitManager::OnRequestResponse() Ranking for Stage %i is complete"),
|
||||||
|
Response.StageID)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (InitPursuit(FoundData))
|
||||||
|
{
|
||||||
|
OnPursuitSequenceReady.Broadcast(NextFocusPursuits, NextFocusPursuits, bFocusIsTruncate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxPursuitManager::OnRequestTimeoutResponse(const FGuid& RequestId, const FString& TimeoutMessage)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Request Timeout [%s]"), *TimeoutMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxPursuitManager::OnRequestError(const FGuid& RequestId, const FString& ErrorMessage)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Request Error [%s]"), *ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxPursuitManager::InitSubSystems()
|
||||||
|
{
|
||||||
|
if (NetworkSubsystem)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
NetworkSubsystem = GEngine->GetEngineSubsystem<UDTFluxNetworkSubsystem>();
|
||||||
|
return NetworkSubsystem != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxPursuitManager::InitPursuit(FRequestData Data)
|
||||||
|
{
|
||||||
|
//Clean Data
|
||||||
|
NextFocusPursuits.Empty();
|
||||||
|
NextPursuits.Empty();
|
||||||
|
PursuitGrouped.Empty();
|
||||||
|
TArray<FDTFluxDetailedRankingItem> AllRankings;
|
||||||
|
TArray<FDTFluxPursuitInfo> AllPursuits;
|
||||||
|
TMap<FDateTime, FDTFluxPursuitGroup> TempGroups;
|
||||||
|
|
||||||
|
// Full the Array Of Rankings
|
||||||
|
for (auto& KeyPair : Data.StageRankings)
|
||||||
|
{
|
||||||
|
for (auto StageRanking : KeyPair.Value.Rankings)
|
||||||
|
{
|
||||||
|
int ContestId = KeyPair.Value.ContestId;
|
||||||
|
FDTFluxPursuitInfo PursuitInfo;
|
||||||
|
PursuitInfo.StartTime = StageRanking.StartTime;
|
||||||
|
PursuitInfo.Bib = StageRanking.Bib;
|
||||||
|
PursuitInfo.ContestId = ContestId;
|
||||||
|
AllPursuits.Add(PursuitInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort Rankings
|
||||||
|
// AllPursuits.Sort([](const FDTFluxPursuitInfo& A, const FDTFluxPursuitInfo& B) {
|
||||||
|
// return A.StartTime < B.StartTime;
|
||||||
|
// });
|
||||||
|
|
||||||
|
for (auto& Pursuit : AllPursuits)
|
||||||
|
{
|
||||||
|
if (TempGroups.Contains(Pursuit.StartTime))
|
||||||
|
{
|
||||||
|
TempGroups[Pursuit.StartTime].PursuitGroup.Add(Pursuit);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FDTFluxPursuitGroup Group;
|
||||||
|
Group.StartTimeGlobal = Pursuit.StartTime;
|
||||||
|
Group.PursuitGroup.Add(Pursuit);
|
||||||
|
TempGroups.Add(Pursuit.StartTime, Group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TempGroups.KeySort([](const FDateTime& A, const FDateTime& B)
|
||||||
|
{
|
||||||
|
return A < B;
|
||||||
|
});
|
||||||
|
PursuitGrouped.Reserve(TempGroups.Num());
|
||||||
|
for (const auto& Pair : TempGroups)
|
||||||
|
{
|
||||||
|
PursuitGrouped.Add(Pair.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
PursuitGrouped.Sort([](const FDTFluxPursuitGroup& A, const FDTFluxPursuitGroup& B)
|
||||||
|
{
|
||||||
|
return A.StartTimeGlobal < B.StartTimeGlobal;
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@ -1,21 +1,19 @@
|
|||||||
// Fill out your copyright notice in the Description page of Project Settings.
|
#pragma once
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
|
#include "Containers/Deque.h"
|
||||||
#include "Subsystems/EngineSubsystem.h"
|
#include "Subsystems/EngineSubsystem.h"
|
||||||
#include "Types/Enum/DTfluxCoreEnum.h"
|
|
||||||
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
||||||
#include "Types/Struct/DTFluxTeamListStruct.h"
|
#include "Types/Struct/DTFluxTeamListStruct.h"
|
||||||
#include "Types/Struct/DTFluxRankingStructs.h"
|
#include "Types/Struct/DTFluxRankingStructs.h"
|
||||||
|
#include "Types/Struct/DTFluxSplitSensor.h"
|
||||||
#include "DTFluxCoreSubsystem.generated.h"
|
#include "DTFluxCoreSubsystem.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
class UDTFluxNetworkSubsystem;
|
||||||
|
|
||||||
class UFDTFluxNetworkSubsystem;
|
|
||||||
/** Forward Decl */
|
/** Forward Decl */
|
||||||
class UDTFluxModelAsset;
|
class UDTFluxModelAsset;
|
||||||
|
class UDTFluxPursuitManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -25,36 +23,40 @@ class DTFLUXCORESUBSYSTEM_API UDTFluxCoreSubsystem : public UEngineSubsystem
|
|||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSplitRankings, FDTFluxSplitRankings&, SplitRankings);
|
||||||
|
|
||||||
|
|
||||||
// TSharedPtr<FDTFluxParser> Parser;
|
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSplitRankings, FDateTime, ReceivedAt, TArray<FDTFluxSplitRanking>, SplitRankings);
|
|
||||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
FOnSplitRankings OnSplitRankings;
|
FOnSplitRankings OnSplitRankings;
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnStageRankings, FDateTime, ReceivedAt, TArray<FDTFluxStageRanking>, StageRankings);
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnStageRankings, FDTFluxStageRankings&, StageRankings);
|
||||||
|
|
||||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
FOnStageRankings OnStageRankings;
|
FOnStageRankings OnStageRankings;
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnContestRankings, FDateTime, ReceivedAt, TArray<FDTFluxContestRanking>, ContestRankings);
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnContestRankings, FDTFluxContestRankings&, ContestRankings);
|
||||||
|
|
||||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
FOnContestRankings OnContestRankings;
|
FOnContestRankings OnContestRankings;
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTeamList, FDateTime, ReceivedAt, TArray<FDTFluxParticipant>, TeamList);
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnTeamList);
|
||||||
|
|
||||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
FOnTeamList OnTeamList;
|
FOnTeamList OnTeamList;
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTeamUpdate, FDateTime, ReceivedAt, FDTFluxParticipant, TeamUpdatedList);
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTeamStatusUpdate, FDTFluxParticipant, TeamUpdated);
|
||||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
|
||||||
FOnTeamUpdate OnTeamUpdate;
|
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTeamStatusUpdate, FDateTime, ReceivedAt, FDTFluxParticipant, TeamUpdated);
|
|
||||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
FOnTeamStatusUpdate OnTeamStatusUpdate;
|
FOnTeamStatusUpdate OnTeamStatusUpdate;
|
||||||
|
|
||||||
|
DECLARE_DELEGATE_TwoParams(FOnRequestedStageRankings, const FDTFluxStageKey&, const FDTFluxContestRankings&);
|
||||||
|
FOnRequestedStageRankings OnRequestedStageRankings;
|
||||||
|
//
|
||||||
|
// DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTeamUpdate, FDateTime, ReceivedAt, FDTFluxParticipant, TeamUpdatedList);
|
||||||
|
// UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
|
// FOnTeamUpdate OnTeamUpdate;
|
||||||
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
void SendTeamListRequest();
|
void SendTeamListRequest();
|
||||||
|
|
||||||
@ -76,33 +78,75 @@ public:
|
|||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
void RequestAllSplitRankingOfContest(int InContestId, int InStageId);
|
void RequestAllSplitRankingOfContest(int InContestId, int InStageId);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
FDTFluxStageRankings GetStageRankings(FDTFluxStageKey StageKey);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
void RequestAllSplitRankingOfStage(int InContestId, int InStageId, int InSplitId);
|
void RequestAllSplitRankingOfStage(int InContestId, int InStageId, int InSplitId);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
const FDTFluxParticipant GetParticipant(int InBib);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
void RefreshStorage();
|
void RefreshStorage();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
TArray<int> GetCurrentContestsId();
|
||||||
|
UFUNCTION()
|
||||||
|
TArray<FDTFluxContest> GetCurrentContests();
|
||||||
|
UFUNCTION()
|
||||||
|
TArray<int> GetContestsIdForTime(const FDateTime Time);
|
||||||
|
UFUNCTION()
|
||||||
|
bool GetContestForId(const int Id, FDTFluxContest& OutContest);
|
||||||
|
UFUNCTION()
|
||||||
|
TArray<FDTFluxContest> GetContestsForTime(const FDateTime Time);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void RequestRankingsForStages(const TArray<FDTFluxStage> RequestedStages) const;
|
||||||
|
UFUNCTION()
|
||||||
|
TArray<FDTFluxContest> GetContests();
|
||||||
|
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
void LaunchPursuitSequenceFor(const TArray<int> ContestIds);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// ~Subsystem Interface
|
// ~Subsystem Interface
|
||||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||||
virtual void Deinitialize() override;
|
virtual void Deinitialize() override;
|
||||||
// ~Subsystem Interface
|
// ~Subsystem Interface
|
||||||
private:
|
|
||||||
UFDTFluxNetworkSubsystem* NetworkSubsystem = nullptr;
|
UPROPERTY()
|
||||||
|
UDTFluxPursuitManager* PursuitManager = nullptr;
|
||||||
|
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void ParseRaceData(const FDTFluxRaceData& RaceDataDefinition);
|
void SaveDataStorage();
|
||||||
|
|
||||||
|
private:
|
||||||
|
UDTFluxNetworkSubsystem* NetworkSubsystem = nullptr;
|
||||||
|
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void ParseTeamList(const FDTFluxTeamListDefinition& TeamListDefinition);
|
void ProcessRaceData(const FDTFluxRaceData& RaceDataDefinition);
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void OnDataReceived();
|
void ProcessTeamList(const FDTFluxTeamListDefinition& TeamListDefinition);
|
||||||
|
UFUNCTION()
|
||||||
|
void ProcessContestRanking(const FDTFluxContestRankings& ContestRankings);
|
||||||
|
UFUNCTION()
|
||||||
|
void ProcessStageRanking(const FDTFluxStageRankings& StageRankings);
|
||||||
|
UFUNCTION()
|
||||||
|
void ProcessSplitRanking(const FDTFluxSplitRankings& SplitRankings);
|
||||||
|
UFUNCTION()
|
||||||
|
void ProcessTeamStatusUpdate(const FDTFluxTeamStatusUpdate& NewParticipantStatus);
|
||||||
|
UFUNCTION()
|
||||||
|
void ProcessSplitSensor(const FDTFluxSplitSensorInfo& SplitSensorInfo);
|
||||||
|
UFUNCTION()
|
||||||
|
void ProcessTeamUpdate(const FDTFluxTeamListDefinition& TeamListDefinition);
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void SendRequest(const FString& Message);
|
void SendRequest(const FString& Message);
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void RegisterDelegates();
|
void RegisterDelegates();
|
||||||
|
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
UDTFluxModelAsset* DataStorage = nullptr;
|
UDTFluxModelAsset* DataStorage = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
37
Source/DTFluxCoreSubsystem/Public/DTFluxCoreSubsystemTools.h
Normal file
37
Source/DTFluxCoreSubsystem/Public/DTFluxCoreSubsystemTools.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||||
|
#include "Types/Enum/DTFluxModelEnums.h"
|
||||||
|
#include "Types/Struct/DTFluxRankingStructs.h"
|
||||||
|
#include "DTFluxCoreSubsystemTools.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class DTFLUXCORESUBSYSTEM_API UDTFluxCoreSubsystemTools : public UBlueprintFunctionLibrary
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UFUNCTION(Blueprintable, Category="DTFlux|Core Subsystem|Tools")
|
||||||
|
static void FilterContestRankings(FDTFluxContestRankings& ContestRankings,
|
||||||
|
const EDTFluxSortingRankingType RankingType, TArray<FDTFluxContestRanking>& OutContestRankings, bool bAscendant);
|
||||||
|
|
||||||
|
UFUNCTION(Blueprintable, Category="DTFlux|Core Subsystem|Tools")
|
||||||
|
static void FilterStageRankings(FDTFluxStageRankings& InStageRankings, const EDTFluxSortingRankingType RankingType, FDTFluxStageRankings& OutStageRankings, bool bAscendant = true);
|
||||||
|
|
||||||
|
UFUNCTION(Blueprintable, Category="DTFlux|Core Subsystem|Tools")
|
||||||
|
static void FilterSplitRankings(FDTFluxSplitRankings& SplitRankings, const EDTFluxSortingRankingType RankinType, FDTFluxSplitRankings& OutSplitRankings, bool bAscendant = true);
|
||||||
|
|
||||||
|
UFUNCTION(Blueprintable, Category="DTFlux|Core Subsystem|Tools")
|
||||||
|
static float ConvertTimeStringToSeconds(const FString& TimeString);
|
||||||
|
UFUNCTION(Blueprintable, Category="DTFlux|Core Subsystem|Tools")
|
||||||
|
static bool CompareTimeString(const FString& A_TimeStr, const FString& B_TimeStr, bool bAscendant = true);
|
||||||
|
|
||||||
|
UFUNCTION(Blueprintable, Category="DTFlux|Core Subsystem|Tools")
|
||||||
|
static bool CompareSpeed(const FString& A_SpeedStr, const FString& B_SpeedStr, bool bAscendant=true);
|
||||||
|
};
|
||||||
133
Source/DTFluxCoreSubsystem/Public/DTFluxPursuitManager.h
Normal file
133
Source/DTFluxCoreSubsystem/Public/DTFluxPursuitManager.h
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Subsystems/DTFluxNetworkSubsystem.h"
|
||||||
|
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
||||||
|
#include "Types/Struct/FDTFluxPursuitInfo.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "DTFluxPursuitManager.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
USTRUCT()
|
||||||
|
struct FRequestData
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<FGuid> RequestIds;
|
||||||
|
UPROPERTY()
|
||||||
|
TMap<FGuid, FDTFluxStageRankings> StageRankings;
|
||||||
|
UPROPERTY()
|
||||||
|
int ContestId;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
bool bIsReady = false;
|
||||||
|
|
||||||
|
|
||||||
|
FRequestData() = default;
|
||||||
|
|
||||||
|
FRequestData(const TArray<FGuid>& InRequestIds, const TMap<FGuid, FDTFluxStageRankings>& InStageRankings)
|
||||||
|
: RequestIds(InRequestIds), StageRankings(InStageRankings)
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param RequestId
|
||||||
|
* @param InRankings
|
||||||
|
* @return True if all needed requests have responses
|
||||||
|
*/
|
||||||
|
bool IsWaitingFor(const FGuid& RequestId, const FDTFluxStageRankings& InRankings)
|
||||||
|
{
|
||||||
|
if (!StageRankings.Contains(RequestId))
|
||||||
|
{
|
||||||
|
StageRankings.Add(RequestId, InRankings);
|
||||||
|
}
|
||||||
|
bIsReady = StageRankings.Num() <= RequestIds.Num();
|
||||||
|
return bIsReady;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
USTRUCT()
|
||||||
|
struct FDTFluxPursuitGroup
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<FDTFluxPursuitInfo> PursuitGroup = TArray<FDTFluxPursuitInfo>();
|
||||||
|
UPROPERTY()
|
||||||
|
FDateTime StartTimeGlobal = FDateTime::MinValue();
|
||||||
|
UPROPERTY()
|
||||||
|
bool bHasStarted = false;
|
||||||
|
UPROPERTY()
|
||||||
|
bool bIsFocus = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnPursuitSequenceReady, const TArray<FDTFluxPursuitInfo>,
|
||||||
|
NextFocusPursuits,
|
||||||
|
const TArray<FDTFluxPursuitInfo>, NextPursuit, bool, bIsTrtuncate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
UCLASS(BlueprintType)
|
||||||
|
class DTFLUXCORESUBSYSTEM_API UDTFluxPursuitManager : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UDTFluxPursuitManager(const FObjectInitializer& ObjectInitializer);
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
|
TArray<FDTFluxPursuitInfo> NextFocusPursuits;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
|
TArray<FDTFluxPursuitInfo> NextPursuits;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
|
bool bFocusIsTruncate = false;
|
||||||
|
//
|
||||||
|
// UPROPERTY()
|
||||||
|
// TArray<FDTFluxStage> TargetStages;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
int MaxSimultaneousPursuit = 7;
|
||||||
|
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="DTFlux|Pursuit",
|
||||||
|
meta=(ClampMin="1", ClampMax="60", UIMin="0", UIMax="60"))
|
||||||
|
int MassStartDelay = 10;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<FDTFluxPursuitGroup> PursuitGrouped;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
int CurrentIndex = -1;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintCallable, Category="DTFlux|Pursuit")
|
||||||
|
FOnPursuitSequenceReady OnPursuitSequenceReady;
|
||||||
|
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="pursuit, launch, poursuite"))
|
||||||
|
void LaunchPursuitSequenceFor(const TArray<FDTFluxContest> InContests);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnRequestResponse(const FGuid& RequestId, FDTFluxServerResponse& Response);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnRequestTimeoutResponse(const FGuid& RequestId, const FString& TimeoutMessage);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnRequestError(const FGuid& RequestId, const FString& ErrorMessage);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
bool InitSubSystems();
|
||||||
|
|
||||||
|
private:
|
||||||
|
TArray<FRequestData> PendingRequestData;
|
||||||
|
|
||||||
|
public:
|
||||||
|
UFUNCTION()
|
||||||
|
bool InitPursuit(FRequestData Data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
UDTFluxNetworkSubsystem* NetworkSubsystem = nullptr;
|
||||||
|
};
|
||||||
@ -9,7 +9,7 @@ public class DTFluxNetwork : ModuleRules
|
|||||||
PublicDependencyModuleNames.AddRange(
|
PublicDependencyModuleNames.AddRange(
|
||||||
new string[]
|
new string[]
|
||||||
{
|
{
|
||||||
"Core",
|
"Core"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,24 @@ FDTFluxWebSocketClient::FDTFluxWebSocketClient()
|
|||||||
FDTFluxWebSocketClient::LastId++;
|
FDTFluxWebSocketClient::LastId++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FDTFluxWebSocketClient::CanSend() const
|
||||||
|
{
|
||||||
|
return Ws.IsValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<FDTFluxWebSocketClient> FDTFluxWebSocketClient::GetClient(
|
||||||
|
const TArray<TSharedPtr<FDTFluxWebSocketClient>> InClients, const FName InName)
|
||||||
|
{
|
||||||
|
for(auto Client: InClients)
|
||||||
|
{
|
||||||
|
if(Client->ClientId == InName)
|
||||||
|
{
|
||||||
|
return Client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void FDTFluxWebSocketClient::Connect()
|
void FDTFluxWebSocketClient::Connect()
|
||||||
{
|
{
|
||||||
Ws = FWebSocketsModule::Get().CreateWebSocket(WsAddress);
|
Ws = FWebSocketsModule::Get().CreateWebSocket(WsAddress);
|
||||||
@ -79,9 +97,12 @@ void FDTFluxWebSocketClient::AddError(const FDTFluxWsClientError Error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FDTFluxWebSocketClient::Send(const FString& Message) const
|
void FDTFluxWebSocketClient::Send(const FString& Message) const
|
||||||
|
{
|
||||||
|
if(Ws.IsValid())
|
||||||
{
|
{
|
||||||
Ws->Send(Message);
|
Ws->Send(Message);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void FDTFluxWebSocketClient::Bind_Internal()
|
void FDTFluxWebSocketClient::Bind_Internal()
|
||||||
|
|||||||
439
Source/DTFluxNetwork/Private/DTFluxQueuedManager.cpp
Normal file
439
Source/DTFluxNetwork/Private/DTFluxQueuedManager.cpp
Normal file
@ -0,0 +1,439 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#include "DTFluxQueuedManager.h"
|
||||||
|
#include "DTFluxNetworkModule.h"
|
||||||
|
#include "JsonObjectConverter.h"
|
||||||
|
|
||||||
|
|
||||||
|
const FString FDTFluxQueuedRequest::Serialize() const
|
||||||
|
{
|
||||||
|
FString JSONString;
|
||||||
|
switch (RequestType)
|
||||||
|
{
|
||||||
|
case EDTFluxRequestType::RaceData:
|
||||||
|
|
||||||
|
{
|
||||||
|
FDTFluxRaceDataRequest RaceData;
|
||||||
|
FJsonObjectConverter::UStructToJsonObjectString(RaceData, JSONString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EDTFluxRequestType::TeamList:
|
||||||
|
{
|
||||||
|
const FDTFluxTeamListRequest TeamList;
|
||||||
|
FJsonObjectConverter::UStructToJsonObjectString(TeamList, JSONString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EDTFluxRequestType::ContestRanking:
|
||||||
|
{
|
||||||
|
FDTFluxContestRankingRequest ContestRanking(ContestId);
|
||||||
|
FJsonObjectConverter::UStructToJsonObjectString(ContestRanking, JSONString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EDTFluxRequestType::StageRanking:
|
||||||
|
{
|
||||||
|
FDTFluxStageRankingRequest StageRanking(ContestId, StageId);
|
||||||
|
FJsonObjectConverter::UStructToJsonObjectString(StageRanking, JSONString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EDTFluxRequestType::SplitRanking:
|
||||||
|
{
|
||||||
|
FDTFluxSplitRankingRequest SplitRanking(ContestId, StageId, SplitId);
|
||||||
|
FJsonObjectConverter::UStructToJsonObjectString(SplitRanking, JSONString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
JSONString = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return JSONString;
|
||||||
|
}
|
||||||
|
|
||||||
|
UDTFluxQueuedManager::UDTFluxQueuedManager()
|
||||||
|
: bIsInitialized(false)
|
||||||
|
, CheckInterval(0.5f)
|
||||||
|
, TimeSinceLastCheck(0.0f)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
UDTFluxQueuedManager::~UDTFluxQueuedManager()
|
||||||
|
{
|
||||||
|
ClearAllRequests();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxQueuedManager::Initialize()
|
||||||
|
{
|
||||||
|
if (!bIsInitialized)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Initializing DTFluxQueuedManager"));
|
||||||
|
bIsInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FGuid UDTFluxQueuedManager::QueueRequest(EDTFluxRequestType RequestType, int32 ContestId, int32 StageId, int32 SplitId,
|
||||||
|
const FString& RawMessage)
|
||||||
|
{
|
||||||
|
// Créer la requête avec les structs existants
|
||||||
|
FDTFluxQueuedRequest NewRequest(RequestType, ContestId, StageId, SplitId);
|
||||||
|
NewRequest.RawResponse = RawMessage;
|
||||||
|
|
||||||
|
// Ajouter à la queue des requêtes en attente
|
||||||
|
PendingRequestsQueue.Enqueue(NewRequest);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Queued request %s: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"),
|
||||||
|
*NewRequest.RequestId.ToString(), (int32)RequestType, ContestId, StageId, SplitId);
|
||||||
|
|
||||||
|
return NewRequest.RequestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxQueuedManager::MarkRequestAsError(const FGuid& TargetRequestGuid)
|
||||||
|
{
|
||||||
|
// TODO: Implement a retry mechanism
|
||||||
|
// For now we simply suppress the request and log a message
|
||||||
|
bool bFoundMatch = false;
|
||||||
|
FDTFluxQueuedRequest Request;
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue;
|
||||||
|
while (PendingRequestsQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
if (Request.RequestId == TargetRequestGuid)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error,
|
||||||
|
TEXT("Marked request %s as error: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"),
|
||||||
|
*Request.RequestId.ToString(), (int32)Request.RequestType, Request.ContestId, Request.StageId,
|
||||||
|
Request.SplitId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TempQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (TempQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
PendingRequestsQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
if (bFoundMatch)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("No Request Found with GUID %s"), *TargetRequestGuid.ToString());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxQueuedManager::MarkRequestAsResponded(const FGuid& TargetRequestGuid)
|
||||||
|
{
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue;
|
||||||
|
bool bFoundMatch = false;
|
||||||
|
|
||||||
|
// Parcourir toutes les requêtes en attente
|
||||||
|
FDTFluxQueuedRequest Request;
|
||||||
|
while (PendingRequestsQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
if (!bFoundMatch && Request.RequestId == TargetRequestGuid)
|
||||||
|
{
|
||||||
|
// Marquer comme ayant reçu une réponse
|
||||||
|
Request.bHasReceivedResponse = true;
|
||||||
|
bFoundMatch = true;
|
||||||
|
|
||||||
|
// Ajouter à la queue des requêtes terminées
|
||||||
|
CompletedRequestsQueue.Enqueue(Request);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose,
|
||||||
|
TEXT("Marked request %s as responded: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"),
|
||||||
|
*Request.RequestId.ToString(), (int32)Request.RequestType, Request.ContestId, Request.StageId,
|
||||||
|
Request.SplitId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Remettre dans la queue temporaire
|
||||||
|
TempQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remettre les requêtes non traitées dans la queue principale
|
||||||
|
while (TempQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
PendingRequestsQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bFoundMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxQueuedManager::MarkRequestAsResponded(const FDTFluxQueuedRequest& TargetRequest)
|
||||||
|
{
|
||||||
|
return MarkRequestAsResponded(TargetRequest.RequestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxQueuedManager::IsRequestPending(FGuid& OutRequestId, EDTFluxApiDataType RequestType, int32 ContestId,
|
||||||
|
int32 StageId,
|
||||||
|
int32 SplitId)
|
||||||
|
{
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue;
|
||||||
|
bool bFoundMatch = false;
|
||||||
|
|
||||||
|
// Parcourir toutes les requêtes en attente
|
||||||
|
FDTFluxQueuedRequest Request;
|
||||||
|
while (PendingRequestsQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
// Vérifier si cette requête correspond
|
||||||
|
if (!bFoundMatch && Request.Matches(RequestType, ContestId, StageId, SplitId))
|
||||||
|
{
|
||||||
|
bFoundMatch = true;
|
||||||
|
OutRequestId = Request.RequestId;
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose,
|
||||||
|
TEXT("Found pending request %s: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"),
|
||||||
|
*Request.RequestId.ToString(), (int32)Request.RequestType, Request.ContestId, Request.StageId,
|
||||||
|
Request.SplitId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remettre dans la queue temporaire
|
||||||
|
TempQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remettre toutes les requêtes dans la queue principale
|
||||||
|
while (TempQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
PendingRequestsQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bFoundMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxQueuedRequest* UDTFluxQueuedManager::GetRequestPending(EDTFluxRequestType RequestType, int32 ContestId,
|
||||||
|
int32 StageId, int32 SplitId)
|
||||||
|
{
|
||||||
|
auto SearchInQueue = [&RequestType, ContestId, StageId, SplitId](
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc>& Queue) -> FDTFluxQueuedRequest*
|
||||||
|
{
|
||||||
|
// Copie temporaire de la queue pour la recherche
|
||||||
|
TQueue<FDTFluxQueuedRequest> TempQueue;
|
||||||
|
|
||||||
|
FDTFluxQueuedRequest* FoundItem = nullptr;
|
||||||
|
FDTFluxQueuedRequest Item;
|
||||||
|
while (Queue.Dequeue(Item))
|
||||||
|
{
|
||||||
|
if (Item.RequestType == RequestType && Item.ContestId == ContestId && Item.StageId == StageId && Item.
|
||||||
|
SplitId == SplitId) // Assuming RequestId is your GUID field
|
||||||
|
{
|
||||||
|
FoundItem = &Item;
|
||||||
|
}
|
||||||
|
// Remettre dans la queue temporaire
|
||||||
|
TempQueue.Enqueue(Item);
|
||||||
|
}
|
||||||
|
while (TempQueue.Dequeue(Item))
|
||||||
|
{
|
||||||
|
Queue.Enqueue(Item);
|
||||||
|
}
|
||||||
|
return FoundItem;
|
||||||
|
};
|
||||||
|
return SearchInQueue(PendingRequestsQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FDTFluxQueuedRequest* UDTFluxQueuedManager::GetRequest(const FGuid& SearchedGuid)
|
||||||
|
{
|
||||||
|
auto SearchInQueue = [&SearchedGuid](TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc>& Queue) -> FDTFluxQueuedRequest*
|
||||||
|
{
|
||||||
|
// Copie temporaire de la queue pour la recherche
|
||||||
|
TQueue<FDTFluxQueuedRequest> TempQueue;
|
||||||
|
|
||||||
|
FDTFluxQueuedRequest* FoundItem = nullptr;
|
||||||
|
FDTFluxQueuedRequest Item;
|
||||||
|
while (Queue.Dequeue(Item))
|
||||||
|
{
|
||||||
|
if (Item.RequestId == SearchedGuid) // Assuming RequestId is your GUID field
|
||||||
|
{
|
||||||
|
// Trouver l'élément dans la queue originale
|
||||||
|
// On doit refaire une copie car on ne peut pas retourner l'adresse de 'Item'
|
||||||
|
FoundItem = &Item;
|
||||||
|
}
|
||||||
|
// Remettre dans la queue temporaire
|
||||||
|
TempQueue.Enqueue(Item);
|
||||||
|
}
|
||||||
|
while (TempQueue.Dequeue(Item))
|
||||||
|
{
|
||||||
|
Queue.Enqueue(Item);
|
||||||
|
}
|
||||||
|
return FoundItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Chercher dans chaque queue
|
||||||
|
if (FDTFluxQueuedRequest* Found = SearchInQueue(PendingRequestsQueue))
|
||||||
|
return Found;
|
||||||
|
|
||||||
|
if (const FDTFluxQueuedRequest* Found = SearchInQueue(CompletedRequestsQueue))
|
||||||
|
return Found;
|
||||||
|
|
||||||
|
if (const FDTFluxQueuedRequest* Found = SearchInQueue(TimedOutRequestsQueue))
|
||||||
|
return Found;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 UDTFluxQueuedManager::GetPendingRequestCount()
|
||||||
|
{
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue;
|
||||||
|
int32 Count = 0;
|
||||||
|
|
||||||
|
// Compter les requêtes en attente
|
||||||
|
FDTFluxQueuedRequest Request;
|
||||||
|
while (PendingRequestsQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
Count++;
|
||||||
|
TempQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remettre toutes les requêtes dans la queue principale
|
||||||
|
while (TempQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
PendingRequestsQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 UDTFluxQueuedManager::CleanupTimedOutRequests()
|
||||||
|
{
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue;
|
||||||
|
int32 TimeoutCount = 0;
|
||||||
|
|
||||||
|
// Parcourir toutes les requêtes en attente
|
||||||
|
FDTFluxQueuedRequest Request;
|
||||||
|
while (PendingRequestsQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
if (Request.HasTimedOut())
|
||||||
|
{
|
||||||
|
// Ajouter à la queue des requêtes expirées
|
||||||
|
TimedOutRequestsQueue.Enqueue(Request);
|
||||||
|
TimeoutCount++;
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning,
|
||||||
|
TEXT("Request %s timed out: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"),
|
||||||
|
*Request.RequestId.ToString(), (int32)Request.RequestType, Request.ContestId, Request.StageId,
|
||||||
|
Request.SplitId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Remettre dans la queue temporaire
|
||||||
|
TempQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remettre les requêtes non expirées dans la queue principale
|
||||||
|
while (TempQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
PendingRequestsQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TimeoutCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 UDTFluxQueuedManager::CleanCashedRequests()
|
||||||
|
{
|
||||||
|
int32 CleanedRequestsCount = 0;
|
||||||
|
|
||||||
|
// Queue temporaire pour stocker les requêtes encore valides
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> ValidCompletedRequests;
|
||||||
|
|
||||||
|
// Traiter toutes les requêtes terminées
|
||||||
|
FDTFluxQueuedRequest CompletedRequest;
|
||||||
|
while (CompletedRequestsQueue.Dequeue(CompletedRequest))
|
||||||
|
{
|
||||||
|
// Vérifier si la requête est cacheable et a reçu une réponse
|
||||||
|
if (CompletedRequest.bIsCacheable && CompletedRequest.bHasReceivedResponse)
|
||||||
|
{
|
||||||
|
// Calculer l'âge de la requête en secondes
|
||||||
|
float RequestAge = (FDateTime::Now() - CompletedRequest.CreatedAt).GetTotalSeconds();
|
||||||
|
|
||||||
|
// Vérifier si le cache est encore valide
|
||||||
|
if (RequestAge <= CompletedRequest.CachedValidity)
|
||||||
|
{
|
||||||
|
// Le cache est encore valide, conserver la requête
|
||||||
|
ValidCompletedRequests.Enqueue(CompletedRequest);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Le cache a expiré, compter cette requête comme nettoyée
|
||||||
|
CleanedRequestsCount++;
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Verbose,
|
||||||
|
TEXT("DTFluxQueuedManager: Cleaned expired cached request %s (Age: %.2fs, Validity: %.2fs)"),
|
||||||
|
*CompletedRequest.RequestId.ToString(), RequestAge, CompletedRequest.CachedValidity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Requête non cacheable ou sans réponse, la conserver
|
||||||
|
ValidCompletedRequests.Enqueue(CompletedRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restaurer la queue avec uniquement les requêtes valides
|
||||||
|
while (ValidCompletedRequests.Dequeue(CompletedRequest))
|
||||||
|
{
|
||||||
|
CompletedRequestsQueue.Enqueue(CompletedRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log du résultat si des requêtes ont été nettoyées
|
||||||
|
if (CleanedRequestsCount > 0)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("DTFluxQueuedManager: Cleaned %d expired cached requests"), CleanedRequestsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CleanedRequestsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxQueuedManager::ClearAllRequests()
|
||||||
|
{
|
||||||
|
// Vider toutes les queues
|
||||||
|
FDTFluxQueuedRequest DummyRequest;
|
||||||
|
while (PendingRequestsQueue.Dequeue(DummyRequest))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
while (CompletedRequestsQueue.Dequeue(DummyRequest))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
while (TimedOutRequestsQueue.Dequeue(DummyRequest))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Cleared all pending requests"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UDTFluxQueuedManager::Tick(float DeltaTime)
|
||||||
|
{
|
||||||
|
if (!bIsInitialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incrémenter le temps écoulé
|
||||||
|
TimeSinceLastCheck += DeltaTime;
|
||||||
|
|
||||||
|
// Vérifier si c'est le moment de nettoyer les requêtes expirées
|
||||||
|
if (TimeSinceLastCheck >= CheckInterval)
|
||||||
|
{
|
||||||
|
TimeSinceLastCheck = 0.0f;
|
||||||
|
CleanupTimedOutRequests();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traiter les requêtes expirées
|
||||||
|
FDTFluxQueuedRequest TimedOutRequest;
|
||||||
|
while (TimedOutRequestsQueue.Dequeue(TimedOutRequest))
|
||||||
|
{
|
||||||
|
// Déclencher l'événement pour chaque requête expirée
|
||||||
|
OnRequestTimedOut.Broadcast(TimedOutRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxQueuedManager::IsTickable() const
|
||||||
|
{
|
||||||
|
return bIsInitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
TStatId UDTFluxQueuedManager::GetStatId() const
|
||||||
|
{
|
||||||
|
RETURN_QUICK_DECLARE_CYCLE_STAT(UDTFluxQueuedManager, STATGROUP_Tickables);
|
||||||
|
}
|
||||||
@ -0,0 +1,531 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Struct/DTFluxServerResponseStruct.h"
|
||||||
|
|
||||||
|
// === IMPLÉMENTATION DES CONSTRUCTEURS ===
|
||||||
|
|
||||||
|
FDTFluxServerResponse::FDTFluxServerResponse()
|
||||||
|
{
|
||||||
|
ReceivedAt = FDateTime::Now();
|
||||||
|
ApiDataType = EDTFluxApiDataType::None;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxServerResponse::FDTFluxServerResponse(const FString& JsonMessage, EDTFluxResponseStatus& OutStatus,
|
||||||
|
bool bLogErrors)
|
||||||
|
{
|
||||||
|
ReceivedAt = FDateTime::Now();
|
||||||
|
RawMessage = JsonMessage;
|
||||||
|
|
||||||
|
ParsingStatus = InitializeFromJson(JsonMessage, bLogErrors);
|
||||||
|
OutStatus = ParsingStatus;
|
||||||
|
|
||||||
|
if (bLogErrors && ParsingStatus != EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to create DTFluxServerResponse: %s"), *GetErrorMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxServerResponse FDTFluxServerResponse::CreateFromJson(const FString& JsonMessage, bool bLogErrors)
|
||||||
|
{
|
||||||
|
EDTFluxResponseStatus Status;
|
||||||
|
FDTFluxServerResponse Response(JsonMessage, Status, bLogErrors);
|
||||||
|
|
||||||
|
if (bLogErrors)
|
||||||
|
{
|
||||||
|
if (Status == EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Successfully created DTFluxServerResponse: %s"),
|
||||||
|
*Response.ToDebugString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Created DTFluxServerResponse with issues: %s"),
|
||||||
|
*Response.GetErrorMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response;
|
||||||
|
}
|
||||||
|
|
||||||
|
EDTFluxResponseStatus FDTFluxServerResponse::TryParse(bool bLogErrors)
|
||||||
|
{
|
||||||
|
// Vérifier que le type est présent
|
||||||
|
if (Type.IsEmpty())
|
||||||
|
{
|
||||||
|
if (bLogErrors)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Response missing 'type' field"));
|
||||||
|
ApiDataType = EDTFluxApiDataType::None;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::MissingData;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::UnknownError;
|
||||||
|
// Validation supplémentaire selon le type
|
||||||
|
if (ContainsDataType("race-data"))
|
||||||
|
{
|
||||||
|
if (bLogErrors)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Parsing race-data response"));
|
||||||
|
}
|
||||||
|
ApiDataType = EDTFluxApiDataType::RaceData;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
if (ContainsDataType("team-list"))
|
||||||
|
{
|
||||||
|
ApiDataType = EDTFluxApiDataType::TeamList;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
if (ContainsDataType("contest-ranking"))
|
||||||
|
{
|
||||||
|
if (ContestID == -1)
|
||||||
|
{
|
||||||
|
if (bLogErrors)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Contest-ranking missing ContestID"));
|
||||||
|
}
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::DataError;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
ApiDataType = EDTFluxApiDataType::ContestRanking;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
else if (ContainsDataType("stage-ranking"))
|
||||||
|
{
|
||||||
|
if (ContestID == -1 || StageID == -1)
|
||||||
|
{
|
||||||
|
if (bLogErrors)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Stage-ranking missing ContestID or StageID"));
|
||||||
|
}
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::DataError;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
if (SplitID != -1)
|
||||||
|
{
|
||||||
|
ApiDataType = EDTFluxApiDataType::SplitRanking;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
ApiDataType = EDTFluxApiDataType::StageRanking;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
if (ContainsDataType("status-update"))
|
||||||
|
{
|
||||||
|
ApiDataType = EDTFluxApiDataType::StatusUpdate;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
if (ContainsDataType("split-sensor"))
|
||||||
|
{
|
||||||
|
ApiDataType = EDTFluxApiDataType::SplitSensor;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
if (ContainsDataType("team-update"))
|
||||||
|
{
|
||||||
|
ApiDataType = EDTFluxApiDataType::TeamUpdate;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
return EDTFluxResponseStatus::UnknownError;
|
||||||
|
}
|
||||||
|
|
||||||
|
EDTFluxResponseStatus FDTFluxServerResponse::InitializeFromJson(const FString& JsonMessage, bool bLogErrors)
|
||||||
|
{
|
||||||
|
// Parser le JSON de base
|
||||||
|
if (!FJsonObjectConverter::JsonObjectStringToUStruct(JsonMessage, this, 0, 0, false, &FailureReason))
|
||||||
|
{
|
||||||
|
if (bLogErrors)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("JSON parse error: %s\nMessage: %s"), *FailureReason.ToString(),
|
||||||
|
*JsonMessage);
|
||||||
|
}
|
||||||
|
return EDTFluxResponseStatus::JsonParseError;
|
||||||
|
}
|
||||||
|
// Vérifier si c'est une erreur du serveur
|
||||||
|
if (Code != -1)
|
||||||
|
{
|
||||||
|
if (bLogErrors)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Server error response: Code=%d, Message=%s"), Code, *Message);
|
||||||
|
}
|
||||||
|
return EDTFluxResponseStatus::ServerError;
|
||||||
|
}
|
||||||
|
return TryParse();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FString FDTFluxServerResponse::GetErrorMessage() const
|
||||||
|
{
|
||||||
|
switch (ParsingStatus)
|
||||||
|
{
|
||||||
|
case EDTFluxResponseStatus::Success:
|
||||||
|
return TEXT("No error");
|
||||||
|
|
||||||
|
case EDTFluxResponseStatus::JsonParseError:
|
||||||
|
return FString::Printf(TEXT("JSON parsing failed: %s"), *FailureReason.ToString());
|
||||||
|
|
||||||
|
case EDTFluxResponseStatus::ServerError:
|
||||||
|
return FString::Printf(TEXT("Server error %d: %s"), Code, *Message);
|
||||||
|
|
||||||
|
case EDTFluxResponseStatus::InvalidType:
|
||||||
|
return FString::Printf(TEXT("Invalid or missing response type: '%s'"), *Type);
|
||||||
|
|
||||||
|
case EDTFluxResponseStatus::MissingData:
|
||||||
|
return FString::Printf(TEXT("Missing required data fields for type '%s'"), *Type);
|
||||||
|
|
||||||
|
case EDTFluxResponseStatus::UnknownError:
|
||||||
|
default:
|
||||||
|
return TEXT("Unknown error occurred during parsing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FString FDTFluxServerResponse::ToDebugString() const
|
||||||
|
{
|
||||||
|
return FString::Printf(
|
||||||
|
TEXT("DTFluxServerResponse[Status=%s, Type=%s, Code=%d, Contest=%d, Stage=%d, Split=%d, ReceivedAt=%s]"),
|
||||||
|
*UEnum::GetValueAsString(ParsingStatus), *Type, Code, ContestID, StageID, SplitID, *ReceivedAt.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseTeamListResponse(FDTFluxTeamListDefinition& OutTeamList)
|
||||||
|
{
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Unset;
|
||||||
|
if (!ValidateResponseType(TEXT("team-list")))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a team-list type"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<FJsonObject> JsonObject;
|
||||||
|
if (!ParseJsonObject(JsonObject))
|
||||||
|
{
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TArray<TSharedPtr<FJsonValue>>* DataArray;
|
||||||
|
if (!JsonObject->TryGetArrayField(TEXT("datas"), DataArray))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("No 'datas' array found in team-list response"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::MissingData;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutTeamList.Participants.Empty();
|
||||||
|
for (const TSharedPtr<FJsonValue>& Value : *DataArray)
|
||||||
|
{
|
||||||
|
if (Value->Type == EJson::Object)
|
||||||
|
{
|
||||||
|
const TSharedPtr<FJsonObject> Item = Value->AsObject();
|
||||||
|
FDTFluxParticipant Participant;
|
||||||
|
if (UDTFluxParticipantFactory::CreateFromJsonCpp(Item, Participant))
|
||||||
|
{
|
||||||
|
OutTeamList.Participants.Add(Participant);
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Failed to parse participant from JSON"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParsingStatus = GetParsingStatus();
|
||||||
|
if (ParsingStatus == EDTFluxResponseStatus::Success && OutTeamList.Participants.Num() != 0)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed %d participants from team-list"),
|
||||||
|
OutTeamList.Participants.Num());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (OutTeamList.Participants.Num() == 0)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("No Participant Added"));
|
||||||
|
}
|
||||||
|
if (ParsingStatus != EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse team-list Error : %s"), *GetErrorMessage());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseTeamUpdateResponse(FDTFluxTeamListDefinition& OutTeamUpdate)
|
||||||
|
{
|
||||||
|
return ParseTeamListResponse(OutTeamUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseRaceData(FDTFluxRaceData& OutRaceData)
|
||||||
|
{
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Unset;
|
||||||
|
if (!ValidateResponseType(TEXT("race-data")))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a race-data type"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxRaceDataResponse RaceDataResponse;
|
||||||
|
if (!FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxRaceDataResponse>(RawMessage, &RaceDataResponse))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse race-data JSON: %s"), *RawMessage);
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
OutRaceData.Datas.Empty();
|
||||||
|
for (const auto& Contest : RaceDataResponse.Datas)
|
||||||
|
{
|
||||||
|
FDTFluxContest NewContest;
|
||||||
|
NewContest.Name = Contest.Name;
|
||||||
|
NewContest.ContestId = Contest.Id;
|
||||||
|
NewContest.Date = Contest.Date;
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Processing Contest %d [%s] Starting at %s"),
|
||||||
|
Contest.Id, *Contest.Name, *Contest.Date.ToString());
|
||||||
|
// Satges
|
||||||
|
for (const auto& Stage : Contest.Stages)
|
||||||
|
{
|
||||||
|
FDTFluxStage NewStage;
|
||||||
|
NewStage.StageId = Stage.Id;
|
||||||
|
NewStage.Name = Stage.Name;
|
||||||
|
|
||||||
|
// Construct full Timestamps strings
|
||||||
|
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, Verbose, TEXT("Stage %d [%s]: Start[%s], CutOff[%s], End[%s]"),
|
||||||
|
Stage.Id, *Stage.Name, *NewStage.StartTime.ToString(),
|
||||||
|
*NewStage.CutOff.ToString(), *NewStage.EndTime.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traiter les splits
|
||||||
|
for (const auto& Split : Contest.Splits)
|
||||||
|
{
|
||||||
|
FDTFluxSplit NewSplit;
|
||||||
|
NewSplit.SplitId = Split.Id;
|
||||||
|
NewSplit.Name = Split.Name;
|
||||||
|
NewContest.Splits.Add(NewSplit);
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Split %d [%s]"), Split.Id, *Split.Name);
|
||||||
|
}
|
||||||
|
// Update Contest metadata
|
||||||
|
NewContest.UpdateEndTime();
|
||||||
|
NewContest.UpdateLastStageId();
|
||||||
|
|
||||||
|
OutRaceData.Datas.Add(NewContest);
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed %d contests from race-data"),
|
||||||
|
OutRaceData.Datas.Num());
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseContestRanking(FDTFluxContestRankings& OutContestRankings)
|
||||||
|
{
|
||||||
|
if (!ValidateResponseType(TEXT("contest-ranking")))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a contest-ranking type"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxContestRankingResponse ContestRankingResponse;
|
||||||
|
if (!FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxContestRankingResponse>(
|
||||||
|
RawMessage, &ContestRankingResponse))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse contest-ranking JSON: %s"), *RawMessage);
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutContestRankings.ContestId = ContestRankingResponse.ContestID;
|
||||||
|
OutContestRankings.Rankings.Empty();
|
||||||
|
for (const auto& RankingItem : ContestRankingResponse.Datas)
|
||||||
|
{
|
||||||
|
FDTFluxContestRanking Ranking = RankingItem;
|
||||||
|
OutContestRankings.Rankings.Add(Ranking);
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed contest ranking for Contest %d with %d entries"),
|
||||||
|
OutContestRankings.ContestId, OutContestRankings.Rankings.Num());
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseStageRankingResponse(FDTFluxStageRankings& OutStageRankings)
|
||||||
|
{
|
||||||
|
// UE_LOG(logDTFluxNetwork, Log, TEXT("Response is stage-ranking type %s"), *RawMessage);
|
||||||
|
if (!ValidateResponseType(TEXT("stage-ranking")))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a stage-ranking type"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FDTFluxStageRankingResponse RankingResponse;
|
||||||
|
if (!FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxStageRankingResponse>(RawMessage, &RankingResponse))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse stage-ranking JSON: %s"), *RawMessage);
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Reponse Update"));
|
||||||
|
|
||||||
|
OutStageRankings.ContestId = ContestID;
|
||||||
|
OutStageRankings.StageId = StageID;
|
||||||
|
OutStageRankings.Rankings = static_cast<TArray<FDTFluxDetailedRankingItem>>(RankingResponse.Datas);
|
||||||
|
OutStageRankings.Initialize();
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed stage ranking for Contest %d, Stage %d with %d entries"),
|
||||||
|
OutStageRankings.ContestId, OutStageRankings.StageId, OutStageRankings.Rankings.Num());
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseSplitRankingResponse(FDTFluxSplitRankings& OutSplitRankings)
|
||||||
|
{
|
||||||
|
if (!ValidateResponseType(TEXT("stage-ranking")) || SplitID == -1)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error,
|
||||||
|
TEXT("Response is not a split-ranking type (stage-ranking with SplitID != -1)"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxSplitRankingResponse SplitRankingResponse;
|
||||||
|
if (!FJsonObjectConverter::JsonObjectStringToUStruct<
|
||||||
|
FDTFluxSplitRankingResponse>(RawMessage, &SplitRankingResponse))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse split-ranking JSON: %s"), *RawMessage);
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutSplitRankings.ContestId = ContestID;
|
||||||
|
OutSplitRankings.StageId = StageID;
|
||||||
|
OutSplitRankings.SplitId = SplitID;
|
||||||
|
OutSplitRankings.Rankings = static_cast<TArray<FDTFluxDetailedRankingItem>>(SplitRankingResponse.Datas);
|
||||||
|
UE_LOG(logDTFluxNetwork, Log,
|
||||||
|
TEXT("Successfully parsed split ranking for Contest %d, Stage %d, Split %d with %d entries"),
|
||||||
|
OutSplitRankings.ContestId, OutSplitRankings.StageId, OutSplitRankings.SplitId,
|
||||||
|
OutSplitRankings.Rankings.Num());
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseStatusUpdateResponse(FDTFluxTeamStatusUpdate& OutStatusUpdate)
|
||||||
|
{
|
||||||
|
if (!ValidateResponseType(TEXT("status-update")))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a status-update type"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxTeamStatusUpdate>(RawMessage, &OutStatusUpdate))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse status-update JSON: %s"), *RawMessage);
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed status update for bib %d"), OutStatusUpdate.Bib);
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseSplitSensorResponse(TArray<FDTFluxSplitSensorInfo>& OutSplitSensorInfos)
|
||||||
|
{
|
||||||
|
if (!ValidateResponseType(TEXT("split-sensor")))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a split-sensor type"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxSplitSensorResponse SplitSensorResponse;
|
||||||
|
if (!FJsonObjectConverter::JsonObjectStringToUStruct(RawMessage, &SplitSensorResponse))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse split-sensor JSON: %s"), *RawMessage);
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutSplitSensorInfos.Empty();
|
||||||
|
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;
|
||||||
|
|
||||||
|
OutSplitSensorInfos.Add(NewSplitSensorInfo);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Split sensor info for bib %d in Contest %d, Stage %d, Split %d"),
|
||||||
|
NewSplitSensorInfo.Bib, NewSplitSensorInfo.ContestId, NewSplitSensorInfo.StageId,
|
||||||
|
NewSplitSensorInfo.SplitId);
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed %d split sensor entries"), OutSplitSensorInfos.Num());
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxServerResponse::ShowDebug(const bool bShouldPrintRawMessage) const
|
||||||
|
{
|
||||||
|
FString DebugMsg = FString::Printf(
|
||||||
|
TEXT("DTFluxServerResponse[Type=%s, Code=%d, Contest=%d, Stage=%d, Split=%d, ReceivedAt=%s]"),
|
||||||
|
*Type, Code, ContestID, StageID, SplitID, *ReceivedAt.ToString());
|
||||||
|
if (bShouldPrintRawMessage)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("%s\nRawMessage: \"%s\""), *DebugMsg, *RawMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === MÉTHODES PRIVÉES ===
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseJsonObject(TSharedPtr<FJsonObject>& OutJsonObject) const
|
||||||
|
{
|
||||||
|
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(RawMessage);
|
||||||
|
|
||||||
|
if (!FJsonSerializer::Deserialize(Reader, OutJsonObject) || !OutJsonObject.IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse JSON: %s"), *RawMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ValidateResponseType(const FString& ExpectedType) const
|
||||||
|
{
|
||||||
|
if (!IsValidResponse())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Invalid server response: Code=%d, Message=%s"), Code, *Message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ContainsDataType(ExpectedType))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Expected type '%s' but got '%s'"), *ExpectedType, *Type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@ -5,23 +5,182 @@
|
|||||||
#include "DTFluxCoreModule.h"
|
#include "DTFluxCoreModule.h"
|
||||||
#include "DTFluxNetworkModule.h"
|
#include "DTFluxNetworkModule.h"
|
||||||
#include "DTFluxNetworkSettings.h"
|
#include "DTFluxNetworkSettings.h"
|
||||||
|
#include "DTFluxQueuedManager.h"
|
||||||
|
#include "DTFluxQueuedManager.h"
|
||||||
#include "JsonObjectConverter.h"
|
#include "JsonObjectConverter.h"
|
||||||
#include "Clients/DTFluxHttpClient.h"
|
#include "Clients/DTFluxHttpClient.h"
|
||||||
#include "Clients/DTFluxWebSocketClient.h"
|
#include "Clients/DTFluxWebSocketClient.h"
|
||||||
#include "Struct/DTFluxServerResponseStruct.h"
|
#include "Struct/DTFluxServerResponseStruct.h"
|
||||||
#include "Struct/DTFluxRequestStructs.h"
|
#include "Struct/DTFluxRequestStructs.h"
|
||||||
#include "Struct/DTFluxRaceDataServerResponse.h"
|
#include "Struct/DTFluxRaceDataServerResponse.h"
|
||||||
|
#include "Struct/DTFluxRankingServerResponse.h"
|
||||||
|
#include "Struct/DTFluxSplitSensorServerResponse.h"
|
||||||
#include "Struct/DTFluxTeamListServerResponse.h"
|
#include "Struct/DTFluxTeamListServerResponse.h"
|
||||||
|
#include "Types/Objects/UDTFluxParticipantFactory.h"
|
||||||
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
||||||
|
#include "Types/Struct/DTFluxSplitSensor.h"
|
||||||
|
|
||||||
|
|
||||||
void UFDTFluxNetworkSubsystem::Connect()
|
// === CONNEXION WEBSOCKET ===
|
||||||
|
void UDTFluxNetworkSubsystem::Connect()
|
||||||
{
|
{
|
||||||
WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port));
|
WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port));
|
||||||
WsClient->Connect();
|
WsClient->Connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFDTFluxNetworkSubsystem::SendRequest(const EDTFluxRequestType RequestType, int InContestId, int InStageId,
|
void UDTFluxNetworkSubsystem::Disconnect()
|
||||||
|
{
|
||||||
|
WsClient->Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxNetworkSubsystem::Reconnect()
|
||||||
|
{
|
||||||
|
ReconnectWs(FName("Ws_Client_0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// === REQUÊTES AVEC TRACKING ===
|
||||||
|
|
||||||
|
FGuid UDTFluxNetworkSubsystem::SendTrackedRequest(
|
||||||
|
EDTFluxApiDataType RequestType,
|
||||||
|
int32 ContestId,
|
||||||
|
int32 StageId,
|
||||||
|
int32 SplitId,
|
||||||
|
float TimeoutSeconds)
|
||||||
|
{
|
||||||
|
if (!QueueManager)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("QueueManager is not initialized"));
|
||||||
|
return FGuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier si une requête similaire est déjà en cours (optionnel)
|
||||||
|
if (IsRequestPending(RequestType, ContestId, StageId, SplitId))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning,
|
||||||
|
TEXT("Similar request already pending: Type=%d, Contest=%d, Stage=%d, Split=%d"),
|
||||||
|
(int32)RequestType, ContestId, StageId, SplitId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer et enqueue la requête
|
||||||
|
FGuid RequestId = QueueManager->QueueRequest(RequestType, ContestId, StageId, SplitId);
|
||||||
|
|
||||||
|
// Envoyer immédiatement si possible (le QueueManager gère la queue)
|
||||||
|
if (const FDTFluxQueuedRequest* QueuedRequest = QueueManager->GetRequest(RequestId))
|
||||||
|
{
|
||||||
|
SendQueuedRequest(*QueuedRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Queued tracked request %s: Type=%d, Contest=%d, Stage=%d, Split=%d"),
|
||||||
|
*RequestId.ToString(), (int32)RequestType, ContestId, StageId, SplitId);
|
||||||
|
|
||||||
|
return RequestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGuid UDTFluxNetworkSubsystem::SendTrackedRequestWithCallback(
|
||||||
|
EDTFluxApiDataType RequestType,
|
||||||
|
int32 ContestId,
|
||||||
|
int32 StageId,
|
||||||
|
int32 SplitId,
|
||||||
|
FOnDTFluxTrackedRequestResponse OnCompleted,
|
||||||
|
FOnDTFluxTrackedRequestTimeout OnTimeout,
|
||||||
|
TOptional<FOnDTFluxRequestResponseError> OnError,
|
||||||
|
float TimeoutSeconds)
|
||||||
|
{
|
||||||
|
FGuid RequestId = SendTrackedRequest(RequestType, ContestId, StageId, SplitId, TimeoutSeconds);
|
||||||
|
|
||||||
|
if (RequestId.IsValid())
|
||||||
|
{
|
||||||
|
// Stocker les callbacks pour cette requête
|
||||||
|
if (OnCompleted.IsBound())
|
||||||
|
{
|
||||||
|
PendingCallbacks.Add(RequestId, OnCompleted);
|
||||||
|
}
|
||||||
|
if (OnTimeout.IsBound())
|
||||||
|
{
|
||||||
|
PendingTimeoutCallbacks.Add(RequestId, OnTimeout);
|
||||||
|
}
|
||||||
|
if (OnError.IsSet() && OnError.GetValue().IsBound())
|
||||||
|
{
|
||||||
|
PendingErrorCallbacks.Add(RequestId, OnError.GetValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RequestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool UDTFluxNetworkSubsystem::GetTrackedRequest(const FGuid& RequestId, FDTFluxQueuedRequest& OutRequest) const
|
||||||
|
{
|
||||||
|
if (!QueueManager)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FDTFluxQueuedRequest* Request = QueueManager->GetRequest(RequestId);
|
||||||
|
if (Request)
|
||||||
|
{
|
||||||
|
OutRequest = *Request;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FDTFluxQueuedRequest* UDTFluxNetworkSubsystem::GetTrackedRequestPtr(const FGuid& RequestId) const
|
||||||
|
{
|
||||||
|
if (!QueueManager)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return QueueManager->GetRequest(RequestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxNetworkSubsystem::HasRequestReceivedResponse(const FGuid& RequestId) const
|
||||||
|
{
|
||||||
|
FDTFluxQueuedRequest Request;
|
||||||
|
if (GetTrackedRequest(RequestId, Request))
|
||||||
|
{
|
||||||
|
return Request.bHasReceivedResponse;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString UDTFluxNetworkSubsystem::GetRequestResponseData(const FGuid& RequestId) const
|
||||||
|
{
|
||||||
|
FDTFluxQueuedRequest Request;
|
||||||
|
if (GetTrackedRequest(RequestId, Request))
|
||||||
|
{
|
||||||
|
return Request.RawResponse;
|
||||||
|
}
|
||||||
|
return FString();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxNetworkSubsystem::IsRequestPending(EDTFluxRequestType RequestType, int32 ContestId, int32 StageId,
|
||||||
|
int32 SplitId) const
|
||||||
|
{
|
||||||
|
if (!QueueManager)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FGuid OutRequestId;
|
||||||
|
return QueueManager->IsRequestPending(OutRequestId, RequestType, ContestId, StageId, SplitId);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 UDTFluxNetworkSubsystem::GetPendingRequestCount() const
|
||||||
|
{
|
||||||
|
if (!QueueManager)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return QueueManager->GetPendingRequestCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
UDTFluxQueuedManager* UDTFluxNetworkSubsystem::GetQueueManager() const
|
||||||
|
{
|
||||||
|
return QueueManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UDTFluxNetworkSubsystem::SendRequest(const EDTFluxRequestType RequestType, int InContestId, int InStageId,
|
||||||
int InSplitId)
|
int InSplitId)
|
||||||
{
|
{
|
||||||
FString Message;
|
FString Message;
|
||||||
@ -34,7 +193,8 @@ void UFDTFluxNetworkSubsystem::SendRequest(const EDTFluxRequestType RequestType,
|
|||||||
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxStageRankingRequest(InContestId, InStageId), Message);
|
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxStageRankingRequest(InContestId, InStageId), Message);
|
||||||
break;
|
break;
|
||||||
case EDTFluxRequestType::SplitRanking:
|
case EDTFluxRequestType::SplitRanking:
|
||||||
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxSplitRankingRequest(InContestId, InStageId, InSplitId), Message);
|
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxSplitRankingRequest(InContestId, InStageId, InSplitId),
|
||||||
|
Message);
|
||||||
break;
|
break;
|
||||||
case EDTFluxRequestType::TeamList:
|
case EDTFluxRequestType::TeamList:
|
||||||
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxTeamListRequest(), Message);
|
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxTeamListRequest(), Message);
|
||||||
@ -51,17 +211,27 @@ void UFDTFluxNetworkSubsystem::SendRequest(const EDTFluxRequestType RequestType,
|
|||||||
SendMessage(Message);
|
SendMessage(Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFDTFluxNetworkSubsystem::SendMessage(const FString& Message)
|
void UDTFluxNetworkSubsystem::SendMessage(const FString& Message)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCore, Warning, TEXT("Sending Message %s"), *Message);
|
||||||
|
|
||||||
|
|
||||||
|
if (WsClient.IsValid() && WsClient->CanSend())
|
||||||
{
|
{
|
||||||
WsClient->Send(Message);
|
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 UFDTFluxNetworkSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
void UDTFluxNetworkSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||||
{
|
{
|
||||||
Super::Initialize(Collection);
|
Super::Initialize(Collection);
|
||||||
FDTFluxCoreModule& DTFluxCore = FModuleManager::Get().LoadModuleChecked<FDTFluxCoreModule>("DTFluxCore");
|
FDTFluxCoreModule& DTFluxCore = FModuleManager::Get().LoadModuleChecked<FDTFluxCoreModule>("DTFluxCore");
|
||||||
FString StatusString = UEnum::GetValueAsString(WsStatus);
|
|
||||||
UE_LOG(logDTFluxNetwork, Log, TEXT("Status is %s"), *StatusString);
|
|
||||||
UDTFluxNetworkSettings* NetworkSettings = GetMutableDefault<UDTFluxNetworkSettings>();
|
UDTFluxNetworkSettings* NetworkSettings = GetMutableDefault<UDTFluxNetworkSettings>();
|
||||||
UDTFluxNetworkSettings::GetWebSocketSettings(NetworkSettings, WsSettings);
|
UDTFluxNetworkSettings::GetWebSocketSettings(NetworkSettings, WsSettings);
|
||||||
UDTFluxNetworkSettings::GetHTTPSettings(NetworkSettings, HttpSettings);
|
UDTFluxNetworkSettings::GetHTTPSettings(NetworkSettings, HttpSettings);
|
||||||
@ -78,24 +248,39 @@ void UFDTFluxNetworkSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
|||||||
WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port));
|
WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port));
|
||||||
WsClient->Connect();
|
WsClient->Connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialisation du Queue Manager
|
||||||
|
QueueManager = NewObject<UDTFluxQueuedManager>(this);
|
||||||
|
QueueManager->Initialize();
|
||||||
|
|
||||||
|
// Connexion au delegate de timeout du Queue Manager
|
||||||
|
QueueManager->OnRequestTimedOut.AddDynamic(this, &UDTFluxNetworkSubsystem::OnRequestTimedOut_Internal);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFDTFluxNetworkSubsystem::Deinitialize()
|
void UDTFluxNetworkSubsystem::Deinitialize()
|
||||||
{
|
{
|
||||||
Super::Deinitialize();
|
Super::Deinitialize();
|
||||||
|
// Nettoyer le Queue Manager
|
||||||
|
if (QueueManager)
|
||||||
|
{
|
||||||
|
QueueManager->OnRequestTimedOut.RemoveDynamic(this, &UDTFluxNetworkSubsystem::OnRequestTimedOut_Internal);
|
||||||
|
QueueManager->ClearAllRequests();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFDTFluxNetworkSubsystem::WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings)
|
// Nettoyer les callbacks
|
||||||
|
PendingCallbacks.Empty();
|
||||||
|
PendingTimeoutCallbacks.Empty();
|
||||||
|
// Déconnexion des clients
|
||||||
|
UnregisterWebSocketEvents();
|
||||||
|
UnregisterHttpEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxNetworkSubsystem::WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings)
|
||||||
{
|
{
|
||||||
// TODO Implement a ClientSelector To retrieve impacted WsClients and populate changes or maybe create a delegate
|
// TODO Implement a ClientSelector To retrieve impacted WsClients and populate changes or maybe create a delegate
|
||||||
bool bNeedsReload = WsSettings != NewWsSettings;
|
bool bNeedsReload = WsSettings != NewWsSettings;
|
||||||
|
|
||||||
WsSettings = NewWsSettings;
|
WsSettings = NewWsSettings;
|
||||||
// UE_LOG(logDTFluxNetwork, Warning, TEXT("WSocket Settings Changed \n\t Address : %s Path : %s Port : %i\n\tbShouldConnectAtStatup : %s, bShouldAutoReconnectOnClosed %s, bShouldAutoReconnectOnError %s"),
|
|
||||||
// *NewWsSettings.Address, *NewWsSettings.Path, NewWsSettings.Port,
|
|
||||||
// NewWsSettings.bShouldConnectAtStartup ? TEXT("True") : TEXT("False"),
|
|
||||||
// NewWsSettings.bShouldAutoReconnectOnClosed ? TEXT("True") : TEXT("False"),
|
|
||||||
// NewWsSettings.bShouldAutoReconnectOnError ? TEXT("True") : TEXT("False") );
|
|
||||||
if (bNeedsReload || WsSettings.bShouldConnectAtStartup)
|
if (bNeedsReload || WsSettings.bShouldConnectAtStartup)
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("WSocket Settings needs Reloding client"))
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("WSocket Settings needs Reloding client"))
|
||||||
@ -104,46 +289,46 @@ void UFDTFluxNetworkSubsystem::WsSettingsChanged(const FDTFluxWsSettings& NewWsS
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void UFDTFluxNetworkSubsystem::HttpSettingsChanged(const FDTFluxHttpSettings& NewHttpSettings)
|
void UDTFluxNetworkSubsystem::HttpSettingsChanged(const FDTFluxHttpSettings& NewHttpSettings)
|
||||||
{
|
{
|
||||||
// TODO Implement a ClientSelector To retrieve impacted HttpClients and populate changes or maybe create a delegate
|
// TODO Implement a ClientSelector To retrieve impacted HttpClients and populate changes or maybe create a delegate
|
||||||
HttpSettings = NewHttpSettings;
|
HttpSettings = NewHttpSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFDTFluxNetworkSubsystem::ReconnectWs(const FName WsClientId)
|
void UDTFluxNetworkSubsystem::ReconnectWs(const FName WsClientId)
|
||||||
{
|
{
|
||||||
FString NewAddress = ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port);
|
FString NewAddress = ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port);
|
||||||
WsClient->SetAddress(NewAddress);
|
WsClient->SetAddress(NewAddress);
|
||||||
WsClient->Reconnect();
|
WsClient->Reconnect();
|
||||||
}
|
}
|
||||||
void UFDTFluxNetworkSubsystem::ReconnectHttp(const FName WsClientId)
|
|
||||||
{
|
|
||||||
|
|
||||||
|
void UDTFluxNetworkSubsystem::ReconnectHttp(const FName WsClientId)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFDTFluxNetworkSubsystem::RegisterWebSocketEvents()
|
void UDTFluxNetworkSubsystem::RegisterWebSocketEvents()
|
||||||
{
|
{
|
||||||
OnWsConnectedEventDelegateHandle =
|
OnWsConnectedEventDelegateHandle =
|
||||||
WsClient->RegisterConnectedEvent().AddUObject(this, &UFDTFluxNetworkSubsystem::OnWebSocketConnected_Subsystem);
|
WsClient->RegisterConnectedEvent().AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketConnected_Subsystem);
|
||||||
OnWsConnectionErrorEventDelegateHandle =
|
OnWsConnectionErrorEventDelegateHandle =
|
||||||
WsClient->RegisterConnectionError()
|
WsClient->RegisterConnectionError()
|
||||||
.AddUObject(this, &UFDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem);
|
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem);
|
||||||
OnWsClosedEventDelegateHandle =
|
OnWsClosedEventDelegateHandle =
|
||||||
WsClient->RegisterClosedEvent()
|
WsClient->RegisterClosedEvent()
|
||||||
.AddUObject(this, &UFDTFluxNetworkSubsystem::OnWebSocketClosedEvent_Subsystem);
|
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketClosedEvent_Subsystem);
|
||||||
OnWsMessageEventDelegateHandle =
|
OnWsMessageEventDelegateHandle =
|
||||||
WsClient->RegisterMessageEvent()
|
WsClient->RegisterMessageEvent()
|
||||||
.AddUObject(this, &UFDTFluxNetworkSubsystem::OnWebSocketMessageEvent_Subsystem);
|
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketMessageEvent_Subsystem);
|
||||||
OnWsMessageSentEventDelegateHandle =
|
OnWsMessageSentEventDelegateHandle =
|
||||||
WsClient->RegisterMessageSentEvent()
|
WsClient->RegisterMessageSentEvent()
|
||||||
.AddUObject(this, &UFDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem);
|
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFDTFluxNetworkSubsystem::RegisterHttpEvents()
|
void UDTFluxNetworkSubsystem::RegisterHttpEvents()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFDTFluxNetworkSubsystem::UnregisterWebSocketEvents()
|
void UDTFluxNetworkSubsystem::UnregisterWebSocketEvents()
|
||||||
{
|
{
|
||||||
if (OnWsConnectedEventDelegateHandle.IsValid())
|
if (OnWsConnectedEventDelegateHandle.IsValid())
|
||||||
{
|
{
|
||||||
@ -151,135 +336,450 @@ void UFDTFluxNetworkSubsystem::UnregisterWebSocketEvents()
|
|||||||
}
|
}
|
||||||
if (OnWsConnectionErrorEventDelegateHandle.IsValid())
|
if (OnWsConnectionErrorEventDelegateHandle.IsValid())
|
||||||
{
|
{
|
||||||
WsClient->UnregisterConnectionError();
|
WsClient->UnregisterConnectionError().Remove(OnWsConnectionErrorEventDelegateHandle);
|
||||||
}
|
}
|
||||||
if (OnWsClosedEventDelegateHandle.IsValid())
|
if (OnWsClosedEventDelegateHandle.IsValid())
|
||||||
{
|
{
|
||||||
WsClient->UnregisterClosedEvent();
|
WsClient->UnregisterClosedEvent().Remove(OnWsClosedEventDelegateHandle);
|
||||||
}
|
}
|
||||||
if (OnWsMessageEventDelegateHandle.IsValid())
|
if (OnWsMessageEventDelegateHandle.IsValid())
|
||||||
{
|
{
|
||||||
WsClient->UnregisterMessageEvent();
|
WsClient->UnregisterMessageEvent().Remove(OnWsMessageEventDelegateHandle);
|
||||||
}
|
}
|
||||||
if (OnWsMessageSentEventDelegateHandle.IsValid())
|
if (OnWsMessageSentEventDelegateHandle.IsValid())
|
||||||
{
|
{
|
||||||
WsClient->UnregisterRawMessageEvent();
|
WsClient->UnregisterRawMessageEvent().Remove(OnWsMessageSentEventDelegateHandle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFDTFluxNetworkSubsystem::UnregisterHttpEvents()
|
void UDTFluxNetworkSubsystem::UnregisterHttpEvents()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFDTFluxNetworkSubsystem::OnWebSocketConnected_Subsystem()
|
void UDTFluxNetworkSubsystem::OnWebSocketConnected_Subsystem()
|
||||||
{
|
{
|
||||||
|
WsStatus = EDTFluxConnectionStatus::Connected;
|
||||||
OnWebSocketConnected.Broadcast();
|
OnWebSocketConnected.Broadcast();
|
||||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws Is Connected with %s"), *WsClient->GetAddress())
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws Is Connected with %s"), *WsClient->GetAddress())
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem(const FString& Error)
|
void UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem(const FString& Error)
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws Error with %s : %s"), *WsClient->GetAddress(), *Error);
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws Error with %s : %s"), *WsClient->GetAddress(), *Error);
|
||||||
|
WsStatus = EDTFluxConnectionStatus::Error;
|
||||||
if (WsSettings.bShouldAutoReconnectOnError)
|
if (WsSettings.bShouldAutoReconnectOnError)
|
||||||
{
|
{
|
||||||
WsClient->Reconnect();
|
WsClient->Reconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFDTFluxNetworkSubsystem::OnWebSocketClosedEvent_Subsystem(int32 StatusCode, const FString& Reason, bool bWasClean)
|
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"),
|
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"));
|
*WsClient->GetAddress(), *Reason, StatusCode, bWasClean ? TEXT("True") : TEXT("False"));
|
||||||
|
WsStatus = EDTFluxConnectionStatus::Closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFDTFluxNetworkSubsystem::OnWebSocketMessageEvent_Subsystem(const FString& MessageString)
|
void UDTFluxNetworkSubsystem::ParseTeamListResponse(FDTFluxServerResponse& Response)
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws %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"))
|
|
||||||
{
|
|
||||||
|
|
||||||
FDTFluxRaceDataResponse RaceData;
|
|
||||||
FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxRaceDataResponse>(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());
|
|
||||||
}
|
|
||||||
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"), RaceDataDefinition.Datas.Num());
|
|
||||||
return OnRaceDataReceived.Broadcast(RaceDataDefinition);
|
|
||||||
}
|
|
||||||
if(Response.Type.Contains("team-list"))
|
|
||||||
{
|
|
||||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws Team-List Data"));
|
|
||||||
FDTFluxTeamListDefinition TeamListDefinition;
|
FDTFluxTeamListDefinition TeamListDefinition;
|
||||||
FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxTeamListDefinition>(Response.RawMessage, &TeamListDefinition);
|
Response.ParseTeamListResponse(TeamListDefinition);
|
||||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws Team-List Data Sent"));
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Parsing Team List Response"));
|
||||||
return OnTeamListReceived.Broadcast(TeamListDefinition);
|
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseTeamListResponse() for JSON Response : %s"), *Response.RawMessage);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("PArsing OK. Sending to Core..."));
|
||||||
UE_LOG(logDTFluxNetwork, Error, TEXT("Ws %s :\nMessage Cannot be Parsed\n%s"), *WsClient->GetAddress(), *MessageString);
|
const bool bIsSuccessfullyBounded = OnTeamListReceived.ExecuteIfBound(TeamListDefinition);
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Inserting %i Participants [%s]"), TeamListDefinition.Participants.Num(),
|
||||||
}
|
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||||
UE_LOG(logDTFluxNetwork, Error, TEXT("Ws %s :\nMessage Received : %s Cannot be Parsed"), *WsClient->GetAddress(), *MessageString);
|
|
||||||
// return DataReceived.Broadcast(Response);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent)
|
void UDTFluxNetworkSubsystem::ParseRaceData(FDTFluxServerResponse& Response)
|
||||||
|
{
|
||||||
|
FDTFluxRaceData RaceData;
|
||||||
|
Response.ParseRaceData(RaceData);
|
||||||
|
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseRaceData() for JSON Response : %s"), *Response.RawMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bool bIsSuccessfullyBounded = OnRaceDataReceived.ExecuteIfBound(RaceData);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Sending %i Contests, [%s]"), RaceData.Datas.Num(),
|
||||||
|
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxNetworkSubsystem::ParseContestRanking(FDTFluxServerResponse& Response)
|
||||||
|
{
|
||||||
|
FDTFluxContestRankings ContestRankings;
|
||||||
|
Response.ParseContestRanking(ContestRankings);
|
||||||
|
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseContestRanking() for JSON Response : %s"), *Response.RawMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bool bIsSuccessfullyBounded = OnContestRankingReceived.ExecuteIfBound(ContestRankings);
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws ContestRanking Data Sent for Contest %i, [%s]"),
|
||||||
|
ContestRankings.ContestId,
|
||||||
|
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxNetworkSubsystem::ParseStageRankingResponse(FDTFluxServerResponse& Response)
|
||||||
|
{
|
||||||
|
FDTFluxStageRankings StageRankings;
|
||||||
|
Response.ParseStageRankingResponse(StageRankings);
|
||||||
|
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseStageRankingResponse() for JSON Response : %s"),
|
||||||
|
*Response.RawMessage);
|
||||||
|
}
|
||||||
|
const bool bIsSuccessfullyBounded = OnStageRankingReceived.ExecuteIfBound(StageRankings);
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("StageRanking Data Sent for Contest %i, Stage %i\n[Result] : %s"),
|
||||||
|
StageRankings.ContestId, StageRankings.StageId,
|
||||||
|
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxNetworkSubsystem::ParseSplitRankingResponse(FDTFluxServerResponse& Response)
|
||||||
|
{
|
||||||
|
FDTFluxSplitRankings SplitRankings;
|
||||||
|
Response.ParseSplitRankingResponse(SplitRankings);
|
||||||
|
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseSplitRankingResponse() for JSON Response : %s"),
|
||||||
|
*Response.RawMessage);
|
||||||
|
}
|
||||||
|
const bool bIsSuccessfullyBounded = OnSplitRankingReceived.ExecuteIfBound(SplitRankings);
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("SplitRanking Data Sent for Contest %i, Stage %i, Split %i\n[Result] : %s"),
|
||||||
|
SplitRankings.ContestId, SplitRankings.StageId, SplitRankings.SplitId,
|
||||||
|
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxNetworkSubsystem::ParseStatusUpdateResponse(FDTFluxServerResponse& Response)
|
||||||
|
{
|
||||||
|
FDTFluxTeamStatusUpdate StatusUpdate;
|
||||||
|
Response.ParseStatusUpdateResponse(StatusUpdate);
|
||||||
|
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseStatusUpdateResponse() for JSON Response : %s"),
|
||||||
|
*Response.RawMessage);
|
||||||
|
}
|
||||||
|
const bool bIsSuccessfullyBounded = OnTeamStatusUpdateReceived.ExecuteIfBound(StatusUpdate);
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("StatusUpdate Data Sent for Bib %i with new status %s\n[Result] : %s"),
|
||||||
|
StatusUpdate.Bib, *UEnum::GetValueAsString(StatusUpdate.Status),
|
||||||
|
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxNetworkSubsystem::ParseSplitSensorResponse(FDTFluxServerResponse& Response)
|
||||||
|
{
|
||||||
|
TArray<FDTFluxSplitSensorInfo> SplitSensorInfos = TArray<FDTFluxSplitSensorInfo>();
|
||||||
|
Response.ParseSplitSensorResponse(SplitSensorInfos);
|
||||||
|
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseSplitSensorResponse() for JSON Response : %s"),
|
||||||
|
*Response.RawMessage);
|
||||||
|
}
|
||||||
|
for (auto& SplitSensorInfo : SplitSensorInfos)
|
||||||
|
{
|
||||||
|
const bool bIsSuccessfullyBounded = OnSplitSensorReceived.ExecuteIfBound(SplitSensorInfo);
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning,
|
||||||
|
TEXT("SplitSensor Data Sent for Bib %i on [Split %i] of [Stage %i] in [Contest %i]\n[Result] : %s"),
|
||||||
|
SplitSensorInfo.Bib, SplitSensorInfo.SplitId, SplitSensorInfo.StageId, SplitSensorInfo.ContestId,
|
||||||
|
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EDTFluxResponseStatus UDTFluxNetworkSubsystem::ProcessPushMessage(FDTFluxServerResponse& Response)
|
||||||
|
{
|
||||||
|
EDTFluxResponseStatus ResponseStatus = EDTFluxResponseStatus::UnknownError;
|
||||||
|
if (DTFluxDataTypeUtils::IsPushOnly(Response.GetResponseType()))
|
||||||
|
{
|
||||||
|
switch (Response.GetResponseType())
|
||||||
|
{
|
||||||
|
case EDTFluxApiDataType::SplitSensor:
|
||||||
|
{
|
||||||
|
TArray<FDTFluxSplitSensorInfo> SplitSensorInfos;
|
||||||
|
if (Response.ParseSplitSensorResponse(SplitSensorInfos))
|
||||||
|
{
|
||||||
|
for (const auto& SplitSensorInfo : SplitSensorInfos)
|
||||||
|
{
|
||||||
|
OnSplitSensorReceived.ExecuteIfBound(SplitSensorInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ResponseStatus = Response.GetParsingStatus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EDTFluxApiDataType::StatusUpdate:
|
||||||
|
{
|
||||||
|
FDTFluxTeamStatusUpdate StatusUpdate;
|
||||||
|
if (Response.ParseStatusUpdateResponse(StatusUpdate))
|
||||||
|
{
|
||||||
|
OnTeamStatusUpdateReceived.ExecuteIfBound(StatusUpdate);
|
||||||
|
}
|
||||||
|
ResponseStatus = Response.GetParsingStatus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EDTFluxApiDataType::TeamUpdate:
|
||||||
|
{
|
||||||
|
FDTFluxTeamListDefinition TeamUpdateList;
|
||||||
|
if (Response.ParseTeamUpdateResponse(TeamUpdateList))
|
||||||
|
{
|
||||||
|
OnTeamUpdateReceived.ExecuteIfBound(TeamUpdateList);
|
||||||
|
}
|
||||||
|
ResponseStatus = Response.GetParsingStatus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
ResponseStatus = EDTFluxResponseStatus::UnknownError;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ResponseStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxNetworkSubsystem::Parse(FDTFluxServerResponse& Response)
|
||||||
|
{
|
||||||
|
EDTFluxResponseStatus ResponseStatus = EDTFluxResponseStatus::Success;
|
||||||
|
switch (Response.GetResponseType())
|
||||||
|
{
|
||||||
|
case EDTFluxApiDataType::RaceData:
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Legacy Parsing RaceData"));
|
||||||
|
ParseRaceData(Response);
|
||||||
|
ResponseStatus = Response.GetParsingStatus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EDTFluxApiDataType::TeamList:
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Legacy Parsing TeamList"));
|
||||||
|
ParseTeamListResponse(Response);
|
||||||
|
ResponseStatus = Response.GetParsingStatus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EDTFluxApiDataType::ContestRanking:
|
||||||
|
{
|
||||||
|
ParseContestRanking(Response);
|
||||||
|
ResponseStatus = Response.GetParsingStatus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EDTFluxApiDataType::StageRanking:
|
||||||
|
{
|
||||||
|
ParseStageRankingResponse(Response);
|
||||||
|
ResponseStatus = Response.GetParsingStatus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EDTFluxApiDataType::SplitRanking:
|
||||||
|
{
|
||||||
|
ParseSplitRankingResponse(Response);
|
||||||
|
ResponseStatus = Response.GetParsingStatus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Legacy Parsing Unknown"));
|
||||||
|
ResponseStatus = EDTFluxResponseStatus::UnknownError;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ResponseStatus != EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("UDTFluxNetworkSubsystem::Parse() Parsing failed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
EDTFluxResponseStatus ResponseStatus;
|
||||||
|
FDTFluxServerResponse Response(MessageString, ResponseStatus);
|
||||||
|
if (!TryMatchResponseToQueuedRequest(Response))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Response %s does not match any queued request"),
|
||||||
|
*UEnum::GetValueAsString(Response.GetResponseType()));
|
||||||
|
if (ProcessPushMessage(Response) != EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Not a push message"));
|
||||||
|
// Legacy
|
||||||
|
Parse(Response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// // if we are here we have a tracked Message
|
||||||
|
// QueueManager->MarkRequestAsResponded()
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent)
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws %s :\nMessage Sent: %s"), *WsClient->GetAddress(), *MessageSent);
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws %s :\nMessage Sent: %s"), *WsClient->GetAddress(), *MessageSent);
|
||||||
}
|
}
|
||||||
|
|
||||||
FString UFDTFluxNetworkSubsystem::ConstructWsAddress(const FString& Address, const FString& Path, const int& Port)
|
bool UDTFluxNetworkSubsystem::CleanRequestCallbacks(const FGuid& RequestId)
|
||||||
|
{
|
||||||
|
bool bCbSuppressSuccess = false;
|
||||||
|
bool bErrorCbSuppressSuccess = false;
|
||||||
|
bool bTimeoutCbSuppressSuccess = false;
|
||||||
|
if (PendingCallbacks.Contains(RequestId))
|
||||||
|
{
|
||||||
|
PendingCallbacks.Remove(RequestId);
|
||||||
|
bCbSuppressSuccess = true;
|
||||||
|
}
|
||||||
|
if (PendingTimeoutCallbacks.Contains(RequestId))
|
||||||
|
{
|
||||||
|
PendingTimeoutCallbacks.Remove(RequestId);
|
||||||
|
bTimeoutCbSuppressSuccess = true;
|
||||||
|
}
|
||||||
|
if (PendingTimeoutCallbacks.Contains(RequestId))
|
||||||
|
{
|
||||||
|
PendingTimeoutCallbacks.Remove(RequestId);
|
||||||
|
bErrorCbSuppressSuccess = true;
|
||||||
|
}
|
||||||
|
return bCbSuppressSuccess && bErrorCbSuppressSuccess && bTimeoutCbSuppressSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxNetworkSubsystem::OnRequestTimedOut_Internal(const FDTFluxQueuedRequest& TimedOutRequest)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Request %s timed out: Type=%d, Contest=%d, Stage=%d, Split=%d"),
|
||||||
|
*TimedOutRequest.RequestId.ToString(),
|
||||||
|
(int32)TimedOutRequest.RequestType,
|
||||||
|
TimedOutRequest.ContestId,
|
||||||
|
TimedOutRequest.StageId,
|
||||||
|
TimedOutRequest.SplitId);
|
||||||
|
|
||||||
|
// Appeler le callback de timeout si présent
|
||||||
|
if (FOnDTFluxTrackedRequestTimeout* TimeoutCallback = PendingTimeoutCallbacks.Find(TimedOutRequest.RequestId))
|
||||||
|
{
|
||||||
|
if (TimeoutCallback->IsBound())
|
||||||
|
{
|
||||||
|
TimeoutCallback->Execute(TimedOutRequest.RequestId, TEXT("Request timeout"));
|
||||||
|
}
|
||||||
|
PendingTimeoutCallbacks.Remove(TimedOutRequest.RequestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nettoyer les callbacks de succès aussi
|
||||||
|
PendingCallbacks.Remove(TimedOutRequest.RequestId);
|
||||||
|
|
||||||
|
// Broadcaster l'événement Blueprint
|
||||||
|
OnTrackedRequestFailed.Broadcast(TimedOutRequest.RequestId, TimedOutRequest.RequestType, TEXT("Request timeout"));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxNetworkSubsystem::TryMatchResponseToQueuedRequest(FDTFluxServerResponse& Response)
|
||||||
|
{
|
||||||
|
if (!QueueManager)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FGuid FoundRequestId;
|
||||||
|
if (QueueManager->IsRequestPending(FoundRequestId, Response.GetResponseType(), Response.ContestID, Response.StageID,
|
||||||
|
Response.SplitID))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Log,
|
||||||
|
TEXT("Matched response to queued request: Type=%s, Contest=%d, Stage=%d, Split=%d"),
|
||||||
|
*UEnum::GetValueAsString(Response.GetResponseType()), Response.ContestID, Response.StageID,
|
||||||
|
Response.SplitID);
|
||||||
|
if (PendingCallbacks.Contains(FoundRequestId))
|
||||||
|
{
|
||||||
|
FOnDTFluxTrackedRequestResponse* SuccessCallback = PendingCallbacks.Find(FoundRequestId);
|
||||||
|
SuccessCallback->ExecuteIfBound(FoundRequestId, Response);
|
||||||
|
//Suppress Callback;
|
||||||
|
return CleanRequestCallbacks(FoundRequestId);
|
||||||
|
}
|
||||||
|
return QueueManager->MarkRequestAsResponded(FoundRequestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxNetworkSubsystem::CompleteTrackedRequest(const FGuid& RequestId, const FString& ResponseData,
|
||||||
|
EDTFluxRequestType RequestType)
|
||||||
|
{
|
||||||
|
// Marquer la requête comme ayant reçu une réponse
|
||||||
|
if (QueueManager)
|
||||||
|
{
|
||||||
|
QueueManager->MarkRequestAsResponded(RequestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appeler le callback de succès si présent
|
||||||
|
if (FOnDTFluxTrackedRequestResponse* SuccessCallback = PendingCallbacks.Find(RequestId))
|
||||||
|
{
|
||||||
|
if (SuccessCallback->IsBound())
|
||||||
|
{
|
||||||
|
EDTFluxResponseStatus ResponseStatus;
|
||||||
|
FDTFluxServerResponse Response(ResponseData, ResponseStatus);
|
||||||
|
if (ResponseStatus == EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
SuccessCallback->Execute(RequestId, Response);
|
||||||
|
QueueManager->MarkRequestAsResponded(RequestId);
|
||||||
|
PendingCallbacks.Remove(RequestId);
|
||||||
|
PendingTimeoutCallbacks.Remove(RequestId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QueueManager->MarkRequestAsError(RequestId);
|
||||||
|
// Fail
|
||||||
|
// FailTrackedRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nettoyer le callback de timeout
|
||||||
|
PendingTimeoutCallbacks.Remove(RequestId);
|
||||||
|
|
||||||
|
// Broadcaster l'événement Blueprint
|
||||||
|
OnTrackedRequestCompleted.Broadcast(RequestId, RequestType, ResponseData);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Completed tracked request %s"), *RequestId.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxNetworkSubsystem::FailTrackedRequest(const FGuid& RequestId, const FString& ErrorMessage,
|
||||||
|
EDTFluxRequestType RequestType)
|
||||||
|
{
|
||||||
|
// Appeler le callback d'erreur si présent
|
||||||
|
if (FOnDTFluxTrackedRequestTimeout* ErrorCallback = PendingTimeoutCallbacks.Find(RequestId))
|
||||||
|
{
|
||||||
|
if (ErrorCallback->IsBound())
|
||||||
|
{
|
||||||
|
ErrorCallback->ExecuteIfBound(RequestId, ErrorMessage);
|
||||||
|
}
|
||||||
|
PendingTimeoutCallbacks.Remove(RequestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nettoyer les callbacks
|
||||||
|
PendingCallbacks.Remove(RequestId);
|
||||||
|
|
||||||
|
// Broadcaster l'événement Blueprint
|
||||||
|
OnTrackedRequestFailed.Broadcast(RequestId, RequestType, ErrorMessage);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed tracked request %s: %s"), *RequestId.ToString(), *ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxNetworkSubsystem::SendQueuedRequest(const FDTFluxQueuedRequest& QueuedRequest)
|
||||||
|
{
|
||||||
|
// Générer le message JSON à partir de la requête
|
||||||
|
FString Message = QueuedRequest.Serialize();
|
||||||
|
|
||||||
|
if (Message.IsEmpty())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to serialize queued request %s"),
|
||||||
|
*QueuedRequest.RequestId.ToString());
|
||||||
|
FailTrackedRequest(QueuedRequest.RequestId, TEXT("Serialization failed"), QueuedRequest.RequestType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dirty trick to fix Case (comme dans l'original)
|
||||||
|
Message = Message.Replace(TEXT("Id"), TEXT("ID"), ESearchCase::CaseSensitive);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Sending queued request %s: %s"), *QueuedRequest.RequestId.ToString(), *Message);
|
||||||
|
|
||||||
|
// Envoyer via WebSocket
|
||||||
|
SendMessage(Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FString UDTFluxNetworkSubsystem::ConstructWsAddress(const FString& Address, const FString& Path, const int& Port)
|
||||||
{
|
{
|
||||||
FString NewAddress;
|
FString NewAddress;
|
||||||
if (!Address.Contains("ws://") && !Address.Contains("wss://"))
|
if (!Address.Contains("ws://") && !Address.Contains("wss://"))
|
||||||
|
|||||||
@ -53,17 +53,11 @@ public:
|
|||||||
|
|
||||||
FDTFluxWebSocketClient();
|
FDTFluxWebSocketClient();
|
||||||
|
|
||||||
static TSharedPtr<FDTFluxWebSocketClient> GetClient(const TArray<TSharedPtr<FDTFluxWebSocketClient>> InClients, const FName InName)
|
bool CanSend() const;
|
||||||
{
|
|
||||||
for(auto Client: InClients)
|
|
||||||
{
|
static TSharedPtr<FDTFluxWebSocketClient> GetClient(const TArray<TSharedPtr<FDTFluxWebSocketClient>> InClients, const FName InName);
|
||||||
if(Client->ClientId == InName)
|
|
||||||
{
|
|
||||||
return Client;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Connect();
|
void Connect();
|
||||||
void Reconnect();
|
void Reconnect();
|
||||||
|
|||||||
66
Source/DTFluxNetwork/Public/DTFluxQueuedManager.h
Normal file
66
Source/DTFluxNetwork/Public/DTFluxQueuedManager.h
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "Containers/Queue.h"
|
||||||
|
#include "Tickable.h"
|
||||||
|
#include "Struct/DTFluxRequestStructs.h"
|
||||||
|
#include "Struct/DTFluxServerResponseStruct.h"
|
||||||
|
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||||
|
#include "DTFluxQueuedManager.generated.h"
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRequestTimedOut, const FDTFluxQueuedRequest&, TimedOutRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Classe Tickable gérant les requêtes WebSockets qui ne sont pas traçables nativement par l'API.
|
||||||
|
* Cette classe utilise TQueue pour gérer efficacement les requêtes en attente et vérifie leur état dans le tick.
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class DTFLUXNETWORK_API UDTFluxQueuedManager : public UObject, public FTickableGameObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Constructeur par défaut */
|
||||||
|
UDTFluxQueuedManager();
|
||||||
|
virtual ~UDTFluxQueuedManager() override;
|
||||||
|
void Initialize();
|
||||||
|
FGuid QueueRequest(EDTFluxRequestType RequestType, int32 ContestId = -1, int32 StageId = -1, int32 SplitId = -1,
|
||||||
|
const FString& RawMessage = "");
|
||||||
|
bool MarkRequestAsError(const FGuid& TargetRequestGuid);
|
||||||
|
bool MarkRequestAsResponded(const FGuid& TargetRequestGuid);
|
||||||
|
bool MarkRequestAsResponded(const FDTFluxQueuedRequest& TargetRequest);
|
||||||
|
bool IsRequestPending(FGuid& OutRequestId, EDTFluxRequestType RequestType, int32 ContestId = -1, int32 StageId = -1,
|
||||||
|
int32 SplitId = -1);
|
||||||
|
FDTFluxQueuedRequest* GetRequestPending(EDTFluxRequestType RequestType, int32 ContestId = -1, int32 StageId = -1,
|
||||||
|
int32 SplitId = -1);
|
||||||
|
const FDTFluxQueuedRequest* GetRequest(const FGuid& SearchedGuid);
|
||||||
|
int32 GetPendingRequestCount();
|
||||||
|
int32 CleanupTimedOutRequests();
|
||||||
|
int32 CleanCashedRequests();
|
||||||
|
void ClearAllRequests();
|
||||||
|
// bool TryProcessResponse(const FDTFluxServerResponse& Response);
|
||||||
|
|
||||||
|
// Interface FTickableGameObject
|
||||||
|
virtual void Tick(float DeltaTime) override;
|
||||||
|
virtual bool IsTickable() const override;
|
||||||
|
virtual TStatId GetStatId() const override;
|
||||||
|
virtual bool IsTickableWhenPaused() const override { return true; }
|
||||||
|
virtual bool IsTickableInEditor() const override { return true; }
|
||||||
|
// Interface ~FTickableGameObject
|
||||||
|
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "DTFlux|Network")
|
||||||
|
FOnRequestTimedOut OnRequestTimedOut;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> PendingRequestsQueue;
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> CompletedRequestsQueue;
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TimedOutRequestsQueue;
|
||||||
|
|
||||||
|
bool bIsInitialized;
|
||||||
|
float CheckInterval;
|
||||||
|
float TimeSinceLastCheck;
|
||||||
|
};
|
||||||
@ -33,7 +33,18 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct DTFLUXNETWORK_API FDTFluxStageRankingResponseItem : public FDTFluxStageRanking
|
struct DTFLUXNETWORK_API FDTFluxStageRankingResponseItem : public FDTFluxDetailedRankingItem
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY()
|
||||||
|
FString Type = "team-stage-ranking";
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct DTFLUXNETWORK_API FDTFluxSplitRankingResponseItem : public FDTFluxDetailedRankingItem
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@ -50,13 +61,24 @@ struct DTFLUXNETWORK_API FDTFluxStageRankingResponse
|
|||||||
public:
|
public:
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Type = "stage-ranking";
|
FString Type = "stage-ranking";
|
||||||
UPROPERTY()
|
|
||||||
int ContestID;
|
|
||||||
UPROPERTY()
|
|
||||||
int StageID;
|
|
||||||
UPROPERTY()
|
|
||||||
int SplitID = -1;
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
// ReSharper disable once IdentifierTypo
|
// ReSharper disable once IdentifierTypo
|
||||||
TArray<FDTFluxStageRankingResponseItem> Datas;
|
TArray<FDTFluxStageRankingResponseItem> Datas;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct DTFLUXNETWORK_API FDTFluxSplitRankingResponse
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY()
|
||||||
|
FString Type = "stage-ranking";
|
||||||
|
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
// ReSharper disable once IdentifierTypo
|
||||||
|
TArray<FDTFluxSplitRankingResponseItem> Datas;
|
||||||
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
|
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||||
#include "UObject/Object.h"
|
#include "UObject/Object.h"
|
||||||
#include "DTFluxRequestStructs.generated.h"
|
#include "DTFluxRequestStructs.generated.h"
|
||||||
|
|
||||||
@ -15,9 +16,13 @@ USTRUCT()
|
|||||||
struct FDTFluxRequestBase
|
struct FDTFluxRequestBase
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Path = "";
|
FString Path = "";
|
||||||
|
|
||||||
|
FDateTime CreatedAt = FDateTime::Now();
|
||||||
|
FGuid RequestId = FGuid::NewGuid();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,8 +33,10 @@ USTRUCT()
|
|||||||
struct FDTFluxRaceDataRequest : public FDTFluxRequestBase
|
struct FDTFluxRaceDataRequest : public FDTFluxRequestBase
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FDTFluxRaceDataRequest(){
|
FDTFluxRaceDataRequest()
|
||||||
|
{
|
||||||
Path = "race-datas";
|
Path = "race-datas";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -42,8 +49,10 @@ USTRUCT()
|
|||||||
struct FDTFluxTeamListRequest : public FDTFluxRequestBase
|
struct FDTFluxTeamListRequest : public FDTFluxRequestBase
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FDTFluxTeamListRequest(){
|
FDTFluxTeamListRequest()
|
||||||
|
{
|
||||||
Path = "team-list";
|
Path = "team-list";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -62,6 +71,7 @@ public:
|
|||||||
Path = "contest-ranking";
|
Path = "contest-ranking";
|
||||||
ContestID = -1;
|
ContestID = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
FDTFluxContestRankingRequest(int InContestID)
|
FDTFluxContestRankingRequest(int InContestID)
|
||||||
{
|
{
|
||||||
Path = "contest-ranking";
|
Path = "contest-ranking";
|
||||||
@ -88,6 +98,7 @@ public:
|
|||||||
StageID = -1;
|
StageID = -1;
|
||||||
SplitID = -1;
|
SplitID = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
FDTFluxStageRankingRequest(int InContestID, int InStageId)
|
FDTFluxStageRankingRequest(int InContestID, int InStageId)
|
||||||
{
|
{
|
||||||
Path = "stage-ranking";
|
Path = "stage-ranking";
|
||||||
@ -102,8 +113,6 @@ public:
|
|||||||
int StageID;
|
int StageID;
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int SplitID;
|
int SplitID;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,6 +131,7 @@ public:
|
|||||||
StageID = -1;
|
StageID = -1;
|
||||||
SplitID = -1;
|
SplitID = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
FDTFluxSplitRankingRequest(int InContestID, int InStageId, int InSplitId)
|
FDTFluxSplitRankingRequest(int InContestID, int InStageId, int InSplitId)
|
||||||
{
|
{
|
||||||
Path = "stage-ranking";
|
Path = "stage-ranking";
|
||||||
@ -129,5 +139,105 @@ public:
|
|||||||
StageID = InStageId;
|
StageID = InStageId;
|
||||||
SplitID = InSplitId;
|
SplitID = InSplitId;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Structure représentant une requête en file d'attente avec ses métadonnées
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FDTFluxQueuedRequest : public FDTFluxRequestBase
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/** L'identifiant unique de la requête */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||||
|
FGuid RequestId;
|
||||||
|
|
||||||
|
/** L'heure à laquelle la requête a été envoyée */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||||
|
FDateTime CreatedAt;
|
||||||
|
|
||||||
|
/** Le type de requête */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||||
|
EDTFluxApiDataType RequestType = EDTFluxRequestType::None;
|
||||||
|
|
||||||
|
/** Identifiant de la compétition (ContestId) */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||||
|
int32 ContestId = -1;
|
||||||
|
|
||||||
|
/** Identifiant de l'étape (StageId) */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||||
|
int32 StageId = -1;
|
||||||
|
|
||||||
|
/** Identifiant du split (SplitId) */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||||
|
int32 SplitId = -1;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||||
|
FString RawResponse = "";
|
||||||
|
|
||||||
|
/** Délai maximum avant que la requête soit considérée comme expirée (en secondes) */
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category = "DTFlux|Request")
|
||||||
|
float TimeoutSeconds = 2.0f;
|
||||||
|
|
||||||
|
/** Determine si la requête peut être mise en cache */
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category = "DTFlux|Request")
|
||||||
|
bool bIsCacheable = false;
|
||||||
|
|
||||||
|
/** Validité du cache si bIsCacheable est mis à true après reception de la réponse (en secondes) */
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category = "DTFlux|Request")
|
||||||
|
float CachedValidity = 50.0f;
|
||||||
|
|
||||||
|
/** Indicateur si la requête a reçu une réponse */
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category = "DTFlux|Request")
|
||||||
|
bool bHasReceivedResponse = false;
|
||||||
|
|
||||||
|
/** Constructeur par défaut */
|
||||||
|
FDTFluxQueuedRequest()
|
||||||
|
{
|
||||||
|
RequestId = FGuid::NewGuid();
|
||||||
|
CreatedAt = FDateTime::Now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constructeur avec paramètres */
|
||||||
|
FDTFluxQueuedRequest(EDTFluxRequestType InRequestType, int32 InContestId = -1, int32 InStageId = -1,
|
||||||
|
int32 InSplitId = -1)
|
||||||
|
: RequestType(InRequestType)
|
||||||
|
, ContestId(InContestId)
|
||||||
|
, StageId(InStageId)
|
||||||
|
, SplitId(InSplitId)
|
||||||
|
{
|
||||||
|
RequestId = FGuid::NewGuid();
|
||||||
|
CreatedAt = FDateTime::Now();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const FDTFluxQueuedRequest& Left) const
|
||||||
|
{
|
||||||
|
return RequestId == Left.RequestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const FDTFluxQueuedRequest& Left) const
|
||||||
|
{
|
||||||
|
return RequestId != Left.RequestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const FString Serialize() const;
|
||||||
|
|
||||||
|
/** Vérifie si la requête a expiré */
|
||||||
|
bool HasTimedOut() const
|
||||||
|
{
|
||||||
|
return (FDateTime::Now() - CreatedAt).GetTotalSeconds() > TimeoutSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Vérifie si cette requête correspond aux paramètres spécifiés */
|
||||||
|
bool Matches(EDTFluxRequestType InRequestType, int32 InContestId = -1, int32 InStageId = -1,
|
||||||
|
int32 InSplitId = -1) const
|
||||||
|
{
|
||||||
|
return RequestType == InRequestType &&
|
||||||
|
(InContestId == -1 || ContestId == InContestId) &&
|
||||||
|
(InStageId == -1 || StageId == InStageId) &&
|
||||||
|
(InSplitId == -1 || SplitId == InSplitId);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,13 +4,38 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "UObject/Object.h"
|
#include "UObject/Object.h"
|
||||||
|
#include "DTFluxNetworkModule.h"
|
||||||
|
#include "DTFluxRaceDataServerResponse.h"
|
||||||
|
#include "DTFluxRankingServerResponse.h"
|
||||||
|
#include "DTFluxSplitSensorServerResponse.h"
|
||||||
|
#include "JsonObjectConverter.h"
|
||||||
|
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||||
|
#include "Types/Objects/UDTFluxParticipantFactory.h"
|
||||||
|
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
||||||
|
#include "Types/Struct/DTFluxRankingStructs.h"
|
||||||
|
#include "Types/Struct/DTFluxSplitSensor.h"
|
||||||
#include "DTFluxServerResponseStruct.generated.h"
|
#include "DTFluxServerResponseStruct.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum pour indiquer le statut du parsing
|
||||||
|
*/
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EDTFluxResponseStatus : uint8
|
||||||
|
{
|
||||||
|
Unset = 0b00000000 UMETA(DisplayName="Unset"),
|
||||||
|
Success = 0b10000000 UMETA(DisplayName="Success"),
|
||||||
|
JsonParseError = 0b00000001 UMETA(DisplayName="JsonParseError"),
|
||||||
|
ServerError = 0b00000010 UMETA(DisplayName="ServerError"),
|
||||||
|
InvalidType = 0b00000100 UMETA(DisplayName="InvalidType"),
|
||||||
|
MissingData = 0b00001000 UMETA(DisplayName="MissingData"),
|
||||||
|
DataError = 0b00010000 UMETA(DisplayName="MissingData"),
|
||||||
|
UnknownError = 0b00100000 UMETA(DisplayName="UnknownError")
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Struct representing a mixed root json server response
|
* Struct representing a mixed root json server response with integrated parsing capabilities
|
||||||
*/
|
*/
|
||||||
USTRUCT()
|
USTRUCT()
|
||||||
struct DTFLUXNETWORK_API FDTFluxServerResponse
|
struct DTFLUXNETWORK_API FDTFluxServerResponse
|
||||||
@ -20,35 +45,99 @@ struct DTFLUXNETWORK_API FDTFluxServerResponse
|
|||||||
public:
|
public:
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Type = "";
|
FString Type = "";
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int Code = -1;
|
int Code = -1;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Message = "";
|
FString Message = "";
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Trigger = "";
|
FString Trigger = "";
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int ContestID = -1;
|
int ContestID = -1;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int StageID = -1;
|
int StageID = -1;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int SplitID = -1;
|
int SplitID = -1;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FDateTime ReceivedAt;
|
FDateTime ReceivedAt;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString RawMessage;
|
FString RawMessage;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FName RequestId = FName("");
|
FName RequestId = FName("");
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FText FailureReason;
|
FText FailureReason;
|
||||||
|
|
||||||
|
// === CONSTRUCTEURS ===
|
||||||
|
FDTFluxServerResponse();
|
||||||
|
FDTFluxServerResponse(const FString& JsonMessage, EDTFluxResponseStatus& OutStatus, bool bLogErrors = true);
|
||||||
|
static FDTFluxServerResponse CreateFromJson(const FString& JsonMessage, bool bLogErrors = true);
|
||||||
|
|
||||||
|
// === MÉTHODES DE PARSING ===
|
||||||
|
|
||||||
|
EDTFluxResponseStatus TryParse(bool bLogErrors = true);
|
||||||
|
|
||||||
|
bool ParseTeamListResponse(FDTFluxTeamListDefinition& OutTeamList);
|
||||||
|
bool ParseTeamUpdateResponse(FDTFluxTeamListDefinition& OutTeamUpdate);
|
||||||
|
bool ParseRaceData(FDTFluxRaceData& OutRaceData);
|
||||||
|
bool ParseContestRanking(FDTFluxContestRankings& OutContestRankings);
|
||||||
|
bool ParseStageRankingResponse(FDTFluxStageRankings& OutStageRankings);
|
||||||
|
bool ParseSplitRankingResponse(FDTFluxSplitRankings& OutSplitRankings);
|
||||||
|
bool ParseStatusUpdateResponse(FDTFluxTeamStatusUpdate& OutStatusUpdate);
|
||||||
|
bool ParseSplitSensorResponse(TArray<FDTFluxSplitSensorInfo>& OutSplitSensorInfos);
|
||||||
|
|
||||||
|
|
||||||
|
// === MÉTHODES UTILITAIRES ===
|
||||||
|
|
||||||
|
bool IsValidResponse() const { return Code == -1; }
|
||||||
|
bool IsSuccessfullyParsed() const { return ParsingStatus == EDTFluxResponseStatus::Success; }
|
||||||
|
EDTFluxResponseStatus GetParsingStatus() const { return ParsingStatus; }
|
||||||
|
EDTFluxApiDataType GetResponseType() const { return ApiDataType; }
|
||||||
|
FString GetDataType() const { return Type; }
|
||||||
|
bool ContainsDataType(const FString& DataType) const { return Type.Contains(DataType); }
|
||||||
|
FString ToDebugString() const;
|
||||||
|
void ShowDebug(const bool bShouldPrintRawMessage = false) const;
|
||||||
|
FString GetErrorMessage() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// === DONNÉES INTERNES ===
|
||||||
|
EDTFluxApiDataType ApiDataType;
|
||||||
|
// Statut du parsing initial
|
||||||
|
EDTFluxResponseStatus ParsingStatus = EDTFluxResponseStatus::Unset;
|
||||||
|
|
||||||
|
// === MÉTHODES PRIVÉES DE PARSING ===
|
||||||
|
bool ParseJsonObject(TSharedPtr<FJsonObject>& OutJsonObject) const;
|
||||||
|
bool ValidateResponseType(const FString& ExpectedType) const;
|
||||||
|
EDTFluxResponseStatus InitializeFromJson(const FString& JsonMessage, bool bLogErrors);
|
||||||
|
|
||||||
|
static FString GetJsonType(const EJson Type)
|
||||||
|
{
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
case EJson::None:
|
||||||
|
return TEXT("None");
|
||||||
|
case EJson::Null:
|
||||||
|
return TEXT("Null");
|
||||||
|
case EJson::String:
|
||||||
|
return TEXT("String");
|
||||||
|
case EJson::Number:
|
||||||
|
return TEXT("Number");
|
||||||
|
case EJson::Boolean:
|
||||||
|
return TEXT("Boolean");
|
||||||
|
case EJson::Array:
|
||||||
|
return TEXT("Array");
|
||||||
|
case EJson::Object:
|
||||||
|
return TEXT("Object");
|
||||||
|
default:
|
||||||
|
return TEXT("Unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -25,10 +25,7 @@ public:
|
|||||||
int SplitID;
|
int SplitID;
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Time = "-";
|
FString Time = "-";
|
||||||
UPROPERTY()
|
|
||||||
FString Gap = "-";
|
|
||||||
UPROPERTY()
|
|
||||||
int Rank;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
|
|||||||
@ -3,60 +3,165 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
|
#include "DTFluxQueuedManager.h"
|
||||||
#include "Struct/DTFluxServerResponseStruct.h"
|
#include "Struct/DTFluxServerResponseStruct.h"
|
||||||
#include "Subsystems/EngineSubsystem.h"
|
#include "Subsystems/EngineSubsystem.h"
|
||||||
#include "Types/DTFluxNetworkSettingsTypes.h"
|
#include "Types/DTFluxNetworkSettingsTypes.h"
|
||||||
#include "Types/Enum/DTfluxCoreEnum.h"
|
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||||
|
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
||||||
|
#include "Types/Struct/DTFluxRankingStructs.h"
|
||||||
|
#include "Types/Struct/DTFluxSplitSensor.h"
|
||||||
|
#include "Types/Struct/DTFluxTeamListStruct.h"
|
||||||
#include "DTFluxNetworkSubsystem.generated.h"
|
#include "DTFluxNetworkSubsystem.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class FDTFluxWebSocketClient;
|
class FDTFluxWebSocketClient;
|
||||||
|
class UDTFluxQueuedManager;
|
||||||
typedef TSharedPtr<FDTFluxWebSocketClient> FDTFluxWebSocketClientSP;
|
typedef TSharedPtr<FDTFluxWebSocketClient> FDTFluxWebSocketClientSP;
|
||||||
class FDTFluxHttpClient;
|
class FDTFluxHttpClient;
|
||||||
typedef TSharedPtr<FDTFluxHttpClient> FDTFluxHttpClientSP;
|
typedef TSharedPtr<FDTFluxHttpClient> FDTFluxHttpClientSP;
|
||||||
|
|
||||||
|
|
||||||
|
// Delegates pour les requêtes avec callback
|
||||||
|
DECLARE_DELEGATE_TwoParams(FOnDTFluxRequestResponseError, const FGuid&, const FString&);
|
||||||
|
DECLARE_DELEGATE_TwoParams(FOnDTFluxTrackedRequestResponse, const FGuid&, FDTFluxServerResponse&);
|
||||||
|
DECLARE_DELEGATE_TwoParams(FOnDTFluxTrackedRequestTimeout, const FGuid&, const FString& /*ErrorMessage*/);
|
||||||
|
// Delegates Blueprint pour les requêtes avec tracking
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnDTFluxTrackedRequestCompleted, const FGuid&, RequestId,
|
||||||
|
EDTFluxApiDataType, RequestType, const FString&, ResponseData);
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnDTFluxTrackedRequestFailed, const FGuid&, RequestId,
|
||||||
|
EDTFluxApiDataType, RequestType, const FString&, ErrorMessage);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
UCLASS(Blueprintable)
|
UCLASS(Blueprintable)
|
||||||
class DTFLUXNETWORK_API UFDTFluxNetworkSubsystem : public UEngineSubsystem
|
class DTFLUXNETWORK_API UDTFluxNetworkSubsystem : public UEngineSubsystem
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
UPROPERTY()
|
||||||
EDTFluxConnectionStatus WsStatus = EDTFluxConnectionStatus::Unset;
|
EDTFluxConnectionStatus WsStatus = EDTFluxConnectionStatus::Unset;
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnWebSocketConnected);
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnWebSocketConnected);
|
||||||
|
|
||||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Network")
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Network")
|
||||||
FOnWebSocketConnected OnWebSocketConnected;
|
FOnWebSocketConnected OnWebSocketConnected;
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRaceDataReceived, const FDTFluxRaceData&, RaceDataDefinition);
|
DECLARE_DELEGATE_OneParam(FOnRaceDataReceived, const FDTFluxRaceData& /*RaceDataDefinition*/);
|
||||||
FOnRaceDataReceived OnRaceDataReceived;
|
FOnRaceDataReceived OnRaceDataReceived;
|
||||||
|
|
||||||
FOnRaceDataReceived& OnReceivedRaceData()
|
FOnRaceDataReceived& OnReceivedRaceData()
|
||||||
{
|
{
|
||||||
return OnRaceDataReceived;
|
return OnRaceDataReceived;
|
||||||
};
|
};
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTeamListReceived, const FDTFluxTeamListDefinition&, TeamListDefinition);
|
// === DELEGATES POUR LES DONNÉES REÇUES (PUSH) ===
|
||||||
|
DECLARE_DELEGATE_OneParam(FOnTeamListReceived, const FDTFluxTeamListDefinition& /*TeamListDefinition*/);
|
||||||
FOnTeamListReceived OnTeamListReceived;
|
FOnTeamListReceived OnTeamListReceived;
|
||||||
|
|
||||||
FOnTeamListReceived& OnReceivedTeamList()
|
FOnTeamListReceived& OnReceivedTeamList()
|
||||||
{
|
{
|
||||||
return OnTeamListReceived;
|
return OnTeamListReceived;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DECLARE_DELEGATE_OneParam(FOnStageRankingReceived, const FDTFluxStageRankings& /*StageRankings*/);
|
||||||
|
FOnStageRankingReceived OnStageRankingReceived;
|
||||||
|
|
||||||
|
FOnStageRankingReceived& OnReceivedStageRanking()
|
||||||
|
{
|
||||||
|
return OnStageRankingReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
DECLARE_DELEGATE_OneParam(FOnSplitRankingReceived, const FDTFluxSplitRankings& /*SplitRankings*/);
|
||||||
|
FOnSplitRankingReceived OnSplitRankingReceived;
|
||||||
|
|
||||||
|
FOnSplitRankingReceived& OnReceivedSplitRanking()
|
||||||
|
{
|
||||||
|
return OnSplitRankingReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
DECLARE_DELEGATE_OneParam(FOnContestRankingReceived, const FDTFluxContestRankings& /*ContestRankings*/);
|
||||||
|
FOnContestRankingReceived OnContestRankingReceived;
|
||||||
|
|
||||||
|
FOnContestRankingReceived& OnReceivedContestRanking()
|
||||||
|
{
|
||||||
|
return OnContestRankingReceived;
|
||||||
|
};
|
||||||
|
|
||||||
|
// === DELEGATES POUR LES DONNÉES REÇUES (PULL) ===
|
||||||
|
DECLARE_DELEGATE_OneParam(FOnSplitSensorReceived, const FDTFluxSplitSensorInfo& /*ContestRankings*/);
|
||||||
|
FOnSplitSensorReceived OnSplitSensorReceived;
|
||||||
|
|
||||||
|
FOnSplitSensorReceived& OnReceivedSplitSensor()
|
||||||
|
{
|
||||||
|
return OnSplitSensorReceived;
|
||||||
|
};
|
||||||
|
|
||||||
|
DECLARE_DELEGATE_OneParam(FOnTeamUpdateReceived, const FDTFluxTeamListDefinition& /*ParticipantToUpdate*/);
|
||||||
|
FOnTeamUpdateReceived OnTeamUpdateReceived;
|
||||||
|
|
||||||
|
FOnTeamUpdateReceived& OnReceivedTeamUpdate()
|
||||||
|
{
|
||||||
|
return OnTeamUpdateReceived;
|
||||||
|
};
|
||||||
|
|
||||||
|
DECLARE_DELEGATE_OneParam(FOnTeamStatusUpdateReceived, const FDTFluxTeamStatusUpdate& /*TeamToUpdate*/);
|
||||||
|
FOnTeamStatusUpdateReceived OnTeamStatusUpdateReceived;
|
||||||
|
|
||||||
|
FOnTeamStatusUpdateReceived& OnReceivedTeamStatusUpdate()
|
||||||
|
{
|
||||||
|
return OnTeamStatusUpdateReceived;
|
||||||
|
};
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
||||||
void Connect();
|
void Connect();
|
||||||
//
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
||||||
// UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
void Disconnect();
|
||||||
// void Reconnect();
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
||||||
|
void Reconnect();
|
||||||
|
|
||||||
|
|
||||||
|
// === REQUÊTES AVEC QUEUE ET TRACKING ===
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||||
|
FGuid SendTrackedRequest(EDTFluxApiDataType RequestType, int32 ContestId = -1, int32 StageId = -1,
|
||||||
|
int32 SplitId = -1, float TimeoutSeconds = 30.0f);
|
||||||
|
|
||||||
|
FGuid SendTrackedRequestWithCallback(EDTFluxApiDataType RequestType, int32 ContestId, int32 StageId, int32 SplitId,
|
||||||
|
FOnDTFluxTrackedRequestResponse OnCompleted,
|
||||||
|
FOnDTFluxTrackedRequestTimeout OnTimeout,
|
||||||
|
TOptional<FOnDTFluxRequestResponseError> OnError = TOptional<
|
||||||
|
FOnDTFluxRequestResponseError>(),
|
||||||
|
float TimeoutSeconds = 30.0f);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||||
|
bool GetTrackedRequest(const FGuid& RequestId, FDTFluxQueuedRequest& OutRequest) const;
|
||||||
|
const FDTFluxQueuedRequest* GetTrackedRequestPtr(const FGuid& RequestId) const;
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests", CallInEditor)
|
||||||
|
bool HasRequestReceivedResponse(const FGuid& RequestId) const;
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||||
|
FString GetRequestResponseData(const FGuid& RequestId) const;
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||||
|
bool IsRequestPending(EDTFluxApiDataType RequestType, int32 ContestId = -1, int32 StageId = -1,
|
||||||
|
int32 SplitId = -1) const;
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||||
|
int32 GetPendingRequestCount() const;
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||||
|
UDTFluxQueuedManager* GetQueueManager() const;
|
||||||
|
|
||||||
|
// === EVENTS BLUEPRINT POUR LE TRACKING ===
|
||||||
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Tracked Requests")
|
||||||
|
FOnDTFluxTrackedRequestCompleted OnTrackedRequestCompleted;
|
||||||
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Tracked Requests")
|
||||||
|
FOnDTFluxTrackedRequestFailed OnTrackedRequestFailed;
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
void SendRequest(const EDTFluxRequestType RequestType, int InContestId = -1, int InStageId = -1, int InSplitId = -1);
|
|
||||||
|
|
||||||
|
|
||||||
|
// === REQUÊTES DIRECTES (LEGACY) ===
|
||||||
|
void SendRequest(const EDTFluxApiDataType RequestType, int InContestId = -1, int InStageId = -1,
|
||||||
|
int InSplitId = -1);
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
||||||
void SendMessage(const FString& Message);
|
void SendMessage(const FString& Message);
|
||||||
|
|
||||||
@ -68,9 +173,23 @@ protected:
|
|||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// === CONFIGURATION ===
|
||||||
FDTFluxWsSettings WsSettings;
|
FDTFluxWsSettings WsSettings;
|
||||||
FDTFluxHttpSettings HttpSettings;
|
FDTFluxHttpSettings HttpSettings;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
UDTFluxQueuedManager* QueueManager;
|
||||||
|
|
||||||
|
// === MAPPING DES CALLBACKS C++ ===
|
||||||
|
TMap<FGuid, FOnDTFluxTrackedRequestResponse> PendingCallbacks;
|
||||||
|
TMap<FGuid, FOnDTFluxTrackedRequestTimeout> PendingTimeoutCallbacks;
|
||||||
|
TMap<FGuid, FOnDTFluxRequestResponseError> PendingErrorCallbacks;
|
||||||
|
|
||||||
|
// === CLIENTS RÉSEAU ===
|
||||||
|
FDTFluxWebSocketClientSP WsClient = nullptr;
|
||||||
|
FDTFluxHttpClientSP HttpClient = nullptr;
|
||||||
|
|
||||||
|
// === MÉTHODES DE CONFIGURATION ===
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings);
|
void WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings);
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
@ -78,23 +197,13 @@ private:
|
|||||||
void ReconnectWs(const FName WsClientId);
|
void ReconnectWs(const FName WsClientId);
|
||||||
void ReconnectHttp(const FName WsClientId);
|
void ReconnectHttp(const FName WsClientId);
|
||||||
|
|
||||||
|
// === GESTION DES ÉVÉNEMENTS WEBSOCKET ===
|
||||||
void RegisterWebSocketEvents();
|
void RegisterWebSocketEvents();
|
||||||
void RegisterHttpEvents();
|
|
||||||
void UnregisterWebSocketEvents();
|
void UnregisterWebSocketEvents();
|
||||||
void UnregisterHttpEvents();
|
|
||||||
|
|
||||||
void OnWebSocketConnected_Subsystem();
|
void OnWebSocketConnected_Subsystem();
|
||||||
void OnWebSocketConnectionError_Subsystem(const FString& Error);
|
void OnWebSocketConnectionError_Subsystem(const FString& Error);
|
||||||
void OnWebSocketClosedEvent_Subsystem(int32 StatusCode, const FString& Reason, bool bWasClean);
|
void OnWebSocketClosedEvent_Subsystem(int32 StatusCode, const FString& Reason, bool bWasClean);
|
||||||
void OnWebSocketMessageEvent_Subsystem(const FString& MessageString);
|
|
||||||
void OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent);
|
|
||||||
|
|
||||||
// TODO : Allow multiple instances of network clients.
|
|
||||||
// // For Future use of Multi-Connections
|
|
||||||
// TArray<FDTFluxWebSocketClientSP> WsClients;
|
|
||||||
// // For Future use of Multi-Connections
|
|
||||||
// TArray<FDTFluxHttpClientSP> HttpClient;
|
|
||||||
// Fo now we jest stick to only one client for each protocol
|
|
||||||
|
|
||||||
FDelegateHandle OnWsConnectedEventDelegateHandle;
|
FDelegateHandle OnWsConnectedEventDelegateHandle;
|
||||||
FDelegateHandle OnWsConnectionErrorEventDelegateHandle;
|
FDelegateHandle OnWsConnectionErrorEventDelegateHandle;
|
||||||
@ -102,9 +211,33 @@ private:
|
|||||||
FDelegateHandle OnWsMessageEventDelegateHandle;
|
FDelegateHandle OnWsMessageEventDelegateHandle;
|
||||||
FDelegateHandle OnWsMessageSentEventDelegateHandle;
|
FDelegateHandle OnWsMessageSentEventDelegateHandle;
|
||||||
|
|
||||||
FDTFluxWebSocketClientSP WsClient = nullptr;
|
// === GESTION DES ÉVÉNEMENTS HTTP ===
|
||||||
FDTFluxHttpClientSP HttpClient = nullptr;
|
void RegisterHttpEvents();
|
||||||
|
void UnregisterHttpEvents();
|
||||||
|
|
||||||
|
// === PARSING DES RÉPONSES ===
|
||||||
|
void ParseTeamListResponse(FDTFluxServerResponse& ServerResponse);
|
||||||
|
void ParseRaceData(FDTFluxServerResponse& Response);
|
||||||
|
void ParseContestRanking(FDTFluxServerResponse& Response);
|
||||||
|
void ParseStageRankingResponse(FDTFluxServerResponse& Response);
|
||||||
|
void ParseSplitRankingResponse(FDTFluxServerResponse& Response);
|
||||||
|
void ParseStatusUpdateResponse(FDTFluxServerResponse& Response);
|
||||||
|
void ParseSplitSensorResponse(FDTFluxServerResponse& Response);
|
||||||
|
EDTFluxResponseStatus ProcessPushMessage(FDTFluxServerResponse& Response);
|
||||||
|
|
||||||
|
void Parse(FDTFluxServerResponse& Response);
|
||||||
|
void OnWebSocketMessageEvent_Subsystem(const FString& MessageString);
|
||||||
|
void OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent);
|
||||||
|
bool CleanRequestCallbacks(const FGuid& RequestId);
|
||||||
|
|
||||||
|
// === GESTION DES REQUÊTES TRACKÉES ===
|
||||||
|
UFUNCTION()
|
||||||
|
void OnRequestTimedOut_Internal(const FDTFluxQueuedRequest& TimedOutRequest);
|
||||||
|
bool TryMatchResponseToQueuedRequest(FDTFluxServerResponse& Response);
|
||||||
|
void CompleteTrackedRequest(const FGuid& RequestId, const FString& ResponseData, EDTFluxRequestType RequestType);
|
||||||
|
void FailTrackedRequest(const FGuid& RequestId, const FString& ErrorMessage, EDTFluxRequestType RequestType);
|
||||||
|
void SendQueuedRequest(const FDTFluxQueuedRequest& QueuedRequest);
|
||||||
|
|
||||||
|
// === UTILITAIRES ===
|
||||||
static FString ConstructWsAddress(const FString& Address, const FString& Path, const int& Port);
|
static FString ConstructWsAddress(const FString& Address, const FString& Path, const int& Port);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -21,7 +21,9 @@ public class DTFluxProjectSettings : ModuleRules
|
|||||||
"Slate",
|
"Slate",
|
||||||
"SlateCore",
|
"SlateCore",
|
||||||
"DeveloperSettings",
|
"DeveloperSettings",
|
||||||
"DTFluxCore"
|
"DTFluxCore",
|
||||||
|
"Settings",
|
||||||
|
"DeveloperSettings"
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -9,7 +9,8 @@ UDTFluxGeneralSettings::UDTFluxGeneralSettings()
|
|||||||
{
|
{
|
||||||
CategoryName = "DTFlux API";
|
CategoryName = "DTFlux API";
|
||||||
|
|
||||||
UE_LOG(logDTFluxProjectSettings, Log, TEXT("ModelAssetLoded isNull() -> %s"), ModelAsset.IsNull() ? TEXT("TRUE") : TEXT("FALSE"));
|
// UE_LOG(logDTFluxProjectSettings, Log, TEXT("ModelAssetLoded isNull() -> %s"), ModelAsset.IsNull() ? TEXT("TRUE") : TEXT("FALSE"));
|
||||||
|
//
|
||||||
|
UE_LOG(logDTFluxProjectSettings, Log, TEXT("Category Name -> %s"), *GetCategoryName().ToString());
|
||||||
|
|
||||||
UE_LOG(logDTFluxProjectSettings, Log, TEXT("ModelAssetLoded IsValid() -> %s"), ModelAsset.IsValid() ? TEXT("TRUE") : TEXT("FALSE"));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,51 @@
|
|||||||
#include "DTFluxProjectSettingsModule.h"
|
#include "DTFluxProjectSettingsModule.h"
|
||||||
|
|
||||||
|
#include "DTFluxGeneralSettings.h"
|
||||||
|
#include "DTFluxNetworkSettings.h"
|
||||||
|
#include "ISettingsModule.h"
|
||||||
|
|
||||||
#define LOCTEXT_NAMESPACE "FDTFluxProjectSettingsModule"
|
#define LOCTEXT_NAMESPACE "FDTFluxProjectSettingsModule"
|
||||||
|
|
||||||
DTFLUXPROJECTSETTINGS_API DEFINE_LOG_CATEGORY(logDTFluxProjectSettings)
|
DTFLUXPROJECTSETTINGS_API DEFINE_LOG_CATEGORY(logDTFluxProjectSettings)
|
||||||
|
|
||||||
void FDTFluxProjectSettingsModule::StartupModule()
|
void FDTFluxProjectSettingsModule::StartupModule()
|
||||||
{
|
{
|
||||||
|
RegisterSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FDTFluxProjectSettingsModule::ShutdownModule()
|
void FDTFluxProjectSettingsModule::ShutdownModule()
|
||||||
{
|
{
|
||||||
|
UnregisterSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxProjectSettingsModule::RegisterSettings()
|
||||||
|
{
|
||||||
|
|
||||||
|
if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
|
||||||
|
{
|
||||||
|
SettingsModule->RegisterSettings("Project", "DTFluxProjectSettings", "DTFluxGeneralSettings",
|
||||||
|
FText::FromString("DTFlux General Settings"),
|
||||||
|
FText::FromString("DTFluxAPI Network Settings"),
|
||||||
|
GetMutableDefault<UDTFluxGeneralSettings>()
|
||||||
|
);
|
||||||
|
SettingsModule->RegisterSettings("Project", "DTFluxProjectSettings", "DTFluxNetworkSettings",
|
||||||
|
FText::FromString("DTFlux Network Settings"),
|
||||||
|
FText::FromString("DTFluxAPI Network Settings"),
|
||||||
|
GetMutableDefault<UDTFluxNetworkSettings>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxProjectSettingsModule::UnregisterSettings()
|
||||||
|
{
|
||||||
|
if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
|
||||||
|
{
|
||||||
|
SettingsModule->UnregisterSettings("Project", "DTFluxProjectSettings", "DTFluxGeneralSettings");
|
||||||
|
SettingsModule->UnregisterSettings("Project", "DTFluxProjectSettings", "DTFluxNetworkSettings");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ class DTFLUXPROJECTSETTINGS_API UDTFluxGeneralSettings : public UDeveloperSettin
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
virtual bool SupportsAutoRegistration() const override{ return false; }
|
||||||
UDTFluxGeneralSettings();
|
UDTFluxGeneralSettings();
|
||||||
UPROPERTY(Category="General", Config, EditAnywhere, BlueprintReadOnly, DisplayName="Datastorage File")
|
UPROPERTY(Category="General", Config, EditAnywhere, BlueprintReadOnly, DisplayName="Datastorage File")
|
||||||
TSoftObjectPtr<UDTFluxModelAsset> ModelAsset;
|
TSoftObjectPtr<UDTFluxModelAsset> ModelAsset;
|
||||||
|
|||||||
@ -48,7 +48,7 @@ public:
|
|||||||
UPROPERTY(Category="CHRONO PROXY|HTTP", Config, EditAnywhere, BlueprintReadOnly, DisplayName="HTTP Port")
|
UPROPERTY(Category="CHRONO PROXY|HTTP", Config, EditAnywhere, BlueprintReadOnly, DisplayName="HTTP Port")
|
||||||
int HTTPPort = 8080;
|
int HTTPPort = 8080;
|
||||||
|
|
||||||
|
virtual bool SupportsAutoRegistration() const override{ return false; }
|
||||||
|
|
||||||
#if WITH_EDITOR
|
#if WITH_EDITOR
|
||||||
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
|
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||||
|
|||||||
@ -21,4 +21,7 @@ public:
|
|||||||
|
|
||||||
virtual void StartupModule() override;
|
virtual void StartupModule() override;
|
||||||
virtual void ShutdownModule() override;
|
virtual void ShutdownModule() override;
|
||||||
|
|
||||||
|
void RegisterSettings();
|
||||||
|
void UnregisterSettings();
|
||||||
};
|
};
|
||||||
|
|||||||
34
Source/DTFluxRaceResult/DTFluxRaceResult.Build.cs
Normal file
34
Source/DTFluxRaceResult/DTFluxRaceResult.Build.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using UnrealBuildTool;
|
||||||
|
|
||||||
|
public class DTFluxRaceResult : ModuleRules
|
||||||
|
{
|
||||||
|
public DTFluxRaceResult(ReadOnlyTargetRules Target) : base(Target)
|
||||||
|
{
|
||||||
|
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
|
||||||
|
PublicDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"Core"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
PrivateDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"CoreUObject",
|
||||||
|
"Engine",
|
||||||
|
"Slate",
|
||||||
|
"SlateCore",
|
||||||
|
"DTFluxProjectSettings",
|
||||||
|
"UMG",
|
||||||
|
"WebBrowser",
|
||||||
|
"Projects",
|
||||||
|
"ToolMenus",
|
||||||
|
"HTTP",
|
||||||
|
"JsonUtilities",
|
||||||
|
"Json"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
161
Source/DTFluxRaceResult/Private/DTFluxRaceResultModule.cpp
Normal file
161
Source/DTFluxRaceResult/Private/DTFluxRaceResultModule.cpp
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
#include "DTFluxRaceResultModule.h"
|
||||||
|
|
||||||
|
#include "LevelEditor.h"
|
||||||
|
#include "Widget/SDTFluxRaceResultWidget.h"
|
||||||
|
#include "Widget/Style/DTFluxRaceResultStyle.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "FDTFluxRaceResultModule"
|
||||||
|
|
||||||
|
DEFINE_LOG_CATEGORY(logDTFluxRaceResult)
|
||||||
|
|
||||||
|
FName DTFLUXRACERESULT_API FDTFluxRaceResult::RaceResultTabId = "DTFluxRaceResult";
|
||||||
|
FText DTFLUXRACERESULT_API FDTFluxRaceResult::RaceResultTabDisplayName = FText::FromString(TEXT("DTFlux RaceResult"));
|
||||||
|
|
||||||
|
|
||||||
|
void DTFLUXRACERESULT_API FDTFluxRaceResult::StartupModule()
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRaceResult, Warning, TEXT("DTFluxRaceResult Module Started"))
|
||||||
|
FDTFluxRaceResultStyle::RegisterStyle();
|
||||||
|
InitMenuExtension();
|
||||||
|
RegisterRaceResultTab();
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma region MenuExtension
|
||||||
|
|
||||||
|
|
||||||
|
void DTFLUXRACERESULT_API FDTFluxRaceResult::InitMenuExtension()
|
||||||
|
{
|
||||||
|
UToolMenus::RegisterStartupCallback(
|
||||||
|
FSimpleMulticastDelegate::FDelegate::CreateRaw(this,
|
||||||
|
&FDTFluxRaceResult::RegisterMenuExtensions));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DTFLUXRACERESULT_API FDTFluxRaceResult::RegisterMenuExtensions()
|
||||||
|
{
|
||||||
|
|
||||||
|
// Étendre le menu enregistré
|
||||||
|
if (UToolMenu* DTFluxMenu = UToolMenus::Get()->ExtendMenu("DTFlux.MainMenu"))
|
||||||
|
{
|
||||||
|
FToolMenuSection& ToolsSection = DTFluxMenu->FindOrAddSection("Tools");
|
||||||
|
ToolsSection.AddMenuEntry(
|
||||||
|
"DTFluxRaceResult",
|
||||||
|
FText::FromString("RaceResult"),
|
||||||
|
FText::FromString("Launch DTFlux RaceResult Control Panel"),
|
||||||
|
FSlateIcon(FDTFluxRaceResultStyle::GetStyleSetName(), "LevelEditor.Tab.IconRaceResult"),
|
||||||
|
FUIAction(FExecuteAction::CreateRaw(this, &FDTFluxRaceResult::OnButtonClicked))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxRaceResult::CreateSubmenu(UToolMenu* Menu)
|
||||||
|
{
|
||||||
|
// Section 2 : Tools
|
||||||
|
FToolMenuSection& ToolsSection = Menu->FindOrAddSection("Tools");
|
||||||
|
ToolsSection.Label = FText::FromString("Tools");
|
||||||
|
ToolsSection.AddMenuEntry(
|
||||||
|
"DTFluxRaceResult",
|
||||||
|
FText::FromString("RaceResult"),
|
||||||
|
FText::FromString("Launch Race Result WebAdmin Console"),
|
||||||
|
FSlateIcon(FDTFluxRaceResultStyle::GetStyleSetName(), "LevelEditor.Tab.IconRaceResult"),
|
||||||
|
// Adaptez selon votre icône
|
||||||
|
FUIAction(FExecuteAction::CreateRaw(this, &FDTFluxRaceResult::OnButtonClicked))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DTFLUXRACERESULT_API FDTFluxRaceResult::OnButtonClicked()
|
||||||
|
{
|
||||||
|
FGlobalTabmanager::Get()->TryInvokeTab(RaceResultTabId);
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("Race Result Launched"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion EditorTab
|
||||||
|
|
||||||
|
#pragma region
|
||||||
|
void DTFLUXRACERESULT_API FDTFluxRaceResult::RegisterRaceResultTab()
|
||||||
|
{
|
||||||
|
FTabSpawnerEntry& SpawnerEntry =
|
||||||
|
FGlobalTabmanager::Get()->RegisterNomadTabSpawner(
|
||||||
|
RaceResultTabId,
|
||||||
|
FOnSpawnTab::CreateRaw(this, &FDTFluxRaceResult::OnSpawnTab)
|
||||||
|
)
|
||||||
|
.SetDisplayName(RaceResultTabDisplayName)
|
||||||
|
.SetTooltipText(FText::FromString(TEXT("Race Result Control Panel")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedRef<SDockTab> DTFLUXRACERESULT_API FDTFluxRaceResult::OnSpawnTab(const FSpawnTabArgs& SpawnTabArgs)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
SNew(
|
||||||
|
SDockTab
|
||||||
|
)
|
||||||
|
.TabRole(ETabRole::NomadTab)
|
||||||
|
// .ShouldAutosize(true)
|
||||||
|
[
|
||||||
|
SNew(SDTFluxRaceResultWidget)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
void DTFLUXRACERESULT_API FDTFluxRaceResult::ShutdownModule()
|
||||||
|
{
|
||||||
|
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(RaceResultTabId);
|
||||||
|
FDTFluxRaceResultStyle::UnregisterStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// // Dans votre code de debug
|
||||||
|
// void DTFLUXRACERESULT_API FDTFluxRaceResult::DebugMenus()
|
||||||
|
// {
|
||||||
|
// UToolMenus* ToolMenus = UToolMenus::Get();
|
||||||
|
// if (ToolMenus)
|
||||||
|
// {
|
||||||
|
// TArray<FName> MenuNames;
|
||||||
|
// ToolMenus->GetAllMenuNames(MenuNames);
|
||||||
|
//
|
||||||
|
// UE_LOG(logDTFluxRaceResult, Warning, TEXT("=== ALL AVAILABLE MENUS ==="));
|
||||||
|
// for (const FName& MenuName : MenuNames)
|
||||||
|
// {
|
||||||
|
// UE_LOG(logDTFluxRaceResult, Warning, TEXT("Menu: %s"), *MenuName.ToString());
|
||||||
|
//
|
||||||
|
// // Obtenir les sections de chaque menu
|
||||||
|
// UToolMenu* Menu = ToolMenus->FindMenu(MenuName);
|
||||||
|
// if (Menu)
|
||||||
|
// {
|
||||||
|
// for (const FToolMenuSection& Section : Menu->Sections)
|
||||||
|
// {
|
||||||
|
// UE_LOG(logDTFluxRaceResult, Warning, TEXT(" Section: %s"), *Section.Name.ToString());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// UE_LOG(logDTFluxRaceResult, Warning, TEXT("=== END MENU LIST ==="));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// void DTFLUXRACERESULT_API FDTFluxRaceResult::AddMenu(FMenuBarBuilder& MenuBarBuilder)
|
||||||
|
// {
|
||||||
|
// MenuBarBuilder.AddPullDownMenu(
|
||||||
|
// FText::FromString("DTFlux"),
|
||||||
|
// FText::FromString("DTFlux API Tools"),
|
||||||
|
// FNewMenuDelegate::CreateRaw(this, &FDTFluxRaceResult::FillMenu)
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void DTFLUXRACERESULT_API FDTFluxRaceResult::FillMenu(FMenuBuilder& MenuBuilder)
|
||||||
|
// {
|
||||||
|
// MenuBuilder.AddMenuEntry(
|
||||||
|
// FText::FromString("RaceResult ControlPanel"),
|
||||||
|
// FText::FromString("Launch RaceResult Control Panel"),
|
||||||
|
// FSlateIcon(FDTFluxRaceResultStyle::GetStyleSetName(), "LevelEditor.Tab.IconRaceResult"),
|
||||||
|
// FExecuteAction::CreateRaw(this, &FDTFluxRaceResult::OnButtonClicked)
|
||||||
|
// );
|
||||||
|
// MenuBuilder.EndSection();
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|
||||||
|
IMPLEMENT_MODULE(FDTFluxRaceResult, DTFluxRaceResult)
|
||||||
@ -0,0 +1,283 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Widget/SDTFluxRaceResultWidget.h"
|
||||||
|
|
||||||
|
#include "DTFluxRaceResultModule.h"
|
||||||
|
#include "HttpModule.h"
|
||||||
|
#include "IWebBrowserCookieManager.h"
|
||||||
|
#include "IWebBrowserWindow.h"
|
||||||
|
#include "SlateOptMacros.h"
|
||||||
|
#include "WebBrowserModule.h"
|
||||||
|
#include "Interfaces/IHttpRequest.h"
|
||||||
|
#include "Interfaces/IHttpResponse.h"
|
||||||
|
|
||||||
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||||
|
|
||||||
|
void SDTFluxRaceResultWidget::Construct(const FArguments& InArgs)
|
||||||
|
{
|
||||||
|
|
||||||
|
FWebBrowserInitSettings browserInitSettings = FWebBrowserInitSettings();
|
||||||
|
|
||||||
|
IWebBrowserModule::Get().CustomInitialize(browserInitSettings);
|
||||||
|
WindowSettings.InitialURL = TEXT("about:blank");
|
||||||
|
WindowSettings.BrowserFrameRate = 25;
|
||||||
|
|
||||||
|
if (IWebBrowserModule::IsAvailable() && IWebBrowserModule::Get().IsWebModuleAvailable())
|
||||||
|
{
|
||||||
|
IWebBrowserSingleton* WebBrowserSingleton = IWebBrowserModule::Get().GetSingleton();
|
||||||
|
Browser = WebBrowserSingleton->CreateBrowserWindow(WindowSettings);
|
||||||
|
// Browser->OnLoadUrl().BindRaw(this, &SDTFluxRaceResultWidget::OnLoadOverride);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ChildSlot
|
||||||
|
[
|
||||||
|
SNew(SBox)
|
||||||
|
.Padding(5.0f)
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
|
||||||
|
[
|
||||||
|
SNew(SVerticalBox)
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
[
|
||||||
|
SAssignNew(WebBrowser, SWebBrowser, Browser)
|
||||||
|
.ShowControls(true)
|
||||||
|
.SupportsTransparency(true)
|
||||||
|
.OnUrlChanged(this, &SDTFluxRaceResultWidget::OnUrlChanged)
|
||||||
|
.OnBeforeNavigation(this, &SDTFluxRaceResultWidget::OnBeforeNavigation)
|
||||||
|
.OnLoadCompleted(FSimpleDelegate::CreateRaw(this, &SDTFluxRaceResultWidget::OnLoadCompleted))
|
||||||
|
.OnLoadError(FSimpleDelegate::CreateRaw(this, &SDTFluxRaceResultWidget::OnLoadError))
|
||||||
|
.OnLoadStarted(FSimpleDelegate::CreateRaw(this, &SDTFluxRaceResultWidget::OnLoadStarted))
|
||||||
|
.ShowErrorMessage(true)
|
||||||
|
.ShowAddressBar(true)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxRaceResultWidget::OnCookieSet(bool bSuccess)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SDTFluxRaceResultWidget::LoadContentViaHTTP()
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRaceResult, Log, TEXT("Loading initial content via HTTP: %s"), *RaceResultUrl);
|
||||||
|
LoadSpecificURL(RaceResultUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SDTFluxRaceResultWidget::LoadSpecificURL(const FString& Url)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRaceResult, Log, TEXT("Loading via HTTP: %s"), *Url);
|
||||||
|
|
||||||
|
// Créer la requête HTTP
|
||||||
|
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
|
||||||
|
|
||||||
|
// Ajouter l'authentification Basic pour TOUTES les requêtes
|
||||||
|
FString Credentials = Username + TEXT(":") + Password;
|
||||||
|
FString EncodedCredentials = FBase64::Encode(Credentials);
|
||||||
|
|
||||||
|
Request->SetURL(Url);
|
||||||
|
Request->SetVerb("GET");
|
||||||
|
Request->SetHeader("Authorization", "Basic " + EncodedCredentials);
|
||||||
|
Request->SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
|
||||||
|
Request->SetHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
|
||||||
|
Request->SetHeader("Accept-Language", "en-US,en;q=0.5");
|
||||||
|
Request->SetHeader("Accept-Encoding", "gzip, deflate");
|
||||||
|
Request->SetHeader("Connection", "keep-alive");
|
||||||
|
|
||||||
|
Request->OnProcessRequestComplete().BindRaw(this, &SDTFluxRaceResultWidget::OnHTTPContentLoaded);
|
||||||
|
Request->ProcessRequest();
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxRaceResult, Log, TEXT("HTTP request sent with Basic Auth"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxRaceResultWidget::OnHTTPContentLoaded(TSharedPtr<IHttpRequest> Request,
|
||||||
|
TSharedPtr<IHttpResponse> HttpResponse, bool bWasSuccessful)
|
||||||
|
{
|
||||||
|
if (bWasSuccessful && HttpResponse.IsValid())
|
||||||
|
{
|
||||||
|
int32 ResponseCode = HttpResponse->GetResponseCode();
|
||||||
|
UE_LOG(logDTFluxRaceResult, Log, TEXT("HTTP Response Code: %d"), ResponseCode);
|
||||||
|
|
||||||
|
if (ResponseCode == 200)
|
||||||
|
{
|
||||||
|
FString Content = HttpResponse->GetContentAsString();
|
||||||
|
UE_LOG(logDTFluxRaceResult, Log, TEXT("Content loaded successfully, size: %d characters"), Content.Len());
|
||||||
|
|
||||||
|
// Traiter le contenu HTML
|
||||||
|
FString ProcessedContent = ProcessHTMLContent(Content, Request->GetURL());
|
||||||
|
|
||||||
|
// Charger le contenu dans le browser via LoadString
|
||||||
|
if (Browser.IsValid())
|
||||||
|
{
|
||||||
|
Browser->LoadString(ProcessedContent, Request->GetURL());
|
||||||
|
UE_LOG(logDTFluxRaceResult, Log, TEXT("Content loaded into browser"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ResponseCode == 401)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRaceResult, Error, TEXT("Authentication failed - 401 Unauthorized. Check your credentials."));
|
||||||
|
}
|
||||||
|
else if (ResponseCode == 403)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRaceResult, Error, TEXT("Access forbidden - 403 Forbidden"));
|
||||||
|
}
|
||||||
|
else if (ResponseCode == 404)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRaceResult, Error, TEXT("Page not found - 404 Not Found"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRaceResult, Error, TEXT("HTTP request failed with code: %d"), ResponseCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRaceResult, Error, TEXT("HTTP request failed completely"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxRaceResultWidget::OnLoadOverride()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SDTFluxRaceResultWidget::OnUrlChanged(const FText& NewUrl)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRaceResult, Log, TEXT("URL changed to: %s"), *NewUrl.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
FString SDTFluxRaceResultWidget::ProcessHTMLContent(const FString& Content, const FString& BaseUrl)
|
||||||
|
{
|
||||||
|
FString ProcessedContent = Content;
|
||||||
|
|
||||||
|
// Extraire le domaine de base
|
||||||
|
FString BaseDomain = ExtractDomain(BaseUrl);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxRaceResult, Log, TEXT("Processing HTML content, base domain: %s"), *BaseDomain);
|
||||||
|
|
||||||
|
// Convertir tous les liens relatifs en liens absolus
|
||||||
|
ProcessedContent = ProcessedContent.Replace(TEXT("src=\"/"), *FString::Printf(TEXT("src=\"%s/"), *BaseDomain));
|
||||||
|
ProcessedContent = ProcessedContent.Replace(TEXT("href=\"/"), *FString::Printf(TEXT("href=\"%s/"), *BaseDomain));
|
||||||
|
ProcessedContent = ProcessedContent.Replace(TEXT("src='/"), *FString::Printf(TEXT("src='%s/"), *BaseDomain));
|
||||||
|
ProcessedContent = ProcessedContent.Replace(TEXT("href='/"), *FString::Printf(TEXT("href='%s/"), *BaseDomain));
|
||||||
|
|
||||||
|
// Gérer les liens relatifs avec ./
|
||||||
|
ProcessedContent = ProcessedContent.Replace(TEXT("src=\"./"), *FString::Printf(TEXT("src=\"%s/"), *BaseDomain));
|
||||||
|
ProcessedContent = ProcessedContent.Replace(TEXT("href=\"./"), *FString::Printf(TEXT("href=\"%s/"), *BaseDomain));
|
||||||
|
|
||||||
|
// Ajouter une balise base pour aider avec les liens relatifs restants
|
||||||
|
FString BaseTag = FString::Printf(TEXT("<base href=\"%s/\">"), *BaseDomain);
|
||||||
|
if (ProcessedContent.Contains(TEXT("<head>")))
|
||||||
|
{
|
||||||
|
FString HeadReplacement = TEXT("<head>") + BaseTag;
|
||||||
|
ProcessedContent = ProcessedContent.Replace(TEXT("<head>"), *HeadReplacement);
|
||||||
|
}
|
||||||
|
else if (ProcessedContent.Contains(TEXT("<HEAD>")))
|
||||||
|
{
|
||||||
|
FString HeadReplacement = TEXT("<HEAD>") + BaseTag;
|
||||||
|
ProcessedContent = ProcessedContent.Replace(TEXT("<HEAD>"), *HeadReplacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxRaceResult, Log, TEXT("HTML content processed"));
|
||||||
|
return ProcessedContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FString SDTFluxRaceResultWidget::ExtractDomain(const FString& Url)
|
||||||
|
{
|
||||||
|
FString Domain = Url;
|
||||||
|
|
||||||
|
// Enlever le protocole
|
||||||
|
if (Url.StartsWith(TEXT("https://")))
|
||||||
|
{
|
||||||
|
Domain = Url.Mid(8);
|
||||||
|
}
|
||||||
|
else if (Url.StartsWith(TEXT("http://")))
|
||||||
|
{
|
||||||
|
Domain = Url.Mid(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trouver le premier slash (début du path)
|
||||||
|
int32 SlashIndex;
|
||||||
|
if (Domain.FindChar(TEXT('/'), SlashIndex))
|
||||||
|
{
|
||||||
|
Domain = Url.Left(Url.Len() - (Domain.Len() - SlashIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enlever le port si présent
|
||||||
|
FString DomainPart = Domain;
|
||||||
|
if (Domain.StartsWith(TEXT("https://")))
|
||||||
|
{
|
||||||
|
DomainPart = Domain.Mid(8);
|
||||||
|
}
|
||||||
|
else if (Domain.StartsWith(TEXT("http://")))
|
||||||
|
{
|
||||||
|
DomainPart = Domain.Mid(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 ColonIndex;
|
||||||
|
if (DomainPart.FindChar(TEXT(':'), ColonIndex))
|
||||||
|
{
|
||||||
|
Domain = Domain.Left(Domain.Len() - (DomainPart.Len() - ColonIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDTFluxRaceResultWidget::OnBeforeNavigation(const FString& Url, const FWebNavigationRequest& Request)
|
||||||
|
{
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxRaceResult, Log, TEXT("Loading via HTTP: %s"), *Url);
|
||||||
|
|
||||||
|
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> HTTPRequest = FHttpModule::Get().CreateRequest();
|
||||||
|
|
||||||
|
// Authentification pour toutes les requêtes
|
||||||
|
FString Credentials = Username + TEXT(":") + Password;
|
||||||
|
FString EncodedCredentials = FBase64::Encode(Credentials);
|
||||||
|
|
||||||
|
HTTPRequest->SetURL(RaceResultUrl);
|
||||||
|
HTTPRequest->SetVerb("GET");
|
||||||
|
HTTPRequest->SetHeader("Authorization", "Basic " + EncodedCredentials);
|
||||||
|
HTTPRequest->SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||||
|
HTTPRequest->SetHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
||||||
|
|
||||||
|
HTTPRequest->OnProcessRequestComplete().BindRaw(this, &SDTFluxRaceResultWidget::OnHTTPContentLoaded);
|
||||||
|
HTTPRequest->ProcessRequest();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxRaceResultWidget::OnLoadCompleted()
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRaceResult, Log, TEXT("Load Completed"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxRaceResultWidget::OnLoadStarted()
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRaceResult, Log, TEXT("Load Started"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxRaceResultWidget::OnLoadError()
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRaceResult, Log, TEXT("Load Error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxRaceResultWidget::OnBeforeResourceLoad(FString Url, FString ResourceType, FContextRequestHeaders& AdditionalHeaders, const bool AllowUserCredentials)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxRaceResultWidget::SetupBasicAuth()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Widget/Style/DTFluxRaceResultStyle.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "Interfaces/IPluginManager.h"
|
||||||
|
#include "Styling/SlateStyleRegistry.h"
|
||||||
|
#include "Styling/SlateStyleMacros.h"
|
||||||
|
|
||||||
|
#define RootToContentDir Style->RootToContentDir
|
||||||
|
|
||||||
|
TSharedPtr<ISlateStyle> FDTFluxRaceResultStyle::StyleSet = nullptr;
|
||||||
|
|
||||||
|
void FDTFluxRaceResultStyle::RegisterStyle()
|
||||||
|
{
|
||||||
|
if(StyleSet.IsValid()) return;
|
||||||
|
|
||||||
|
StyleSet = Create();
|
||||||
|
FSlateStyleRegistry::RegisterSlateStyle(*StyleSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxRaceResultStyle::UnregisterStyle()
|
||||||
|
{
|
||||||
|
if(StyleSet.IsValid())
|
||||||
|
{
|
||||||
|
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet);
|
||||||
|
ensure(StyleSet.IsUnique());
|
||||||
|
StyleSet.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxRaceResultStyle::ReloadTextures()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<ISlateStyle> FDTFluxRaceResultStyle::Create()
|
||||||
|
{
|
||||||
|
TSharedPtr<FSlateStyleSet> Style = MakeShareable(new FSlateStyleSet("DTFluxRaceResultStyle"));
|
||||||
|
Style->SetContentRoot(IPluginManager::Get().FindPlugin("DTFluxAPI")->GetBaseDir()/TEXT("Resources"));
|
||||||
|
|
||||||
|
Style->Set("LevelEditor.Tab.IconRaceResult", new IMAGE_BRUSH_SVG("DTFluxRaceResult16x16", FVector2d(16)) );
|
||||||
|
return Style;
|
||||||
|
}
|
||||||
|
|
||||||
33
Source/DTFluxRaceResult/Public/DTFluxRaceResultModule.h
Normal file
33
Source/DTFluxRaceResult/Public/DTFluxRaceResultModule.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
|
DTFLUXRACERESULT_API DECLARE_LOG_CATEGORY_EXTERN(logDTFluxRaceResult, All, All);
|
||||||
|
|
||||||
|
class DTFLUXRACERESULT_API FDTFluxRaceResult : public IModuleInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void StartupModule() override;
|
||||||
|
virtual void ShutdownModule() override;
|
||||||
|
|
||||||
|
#pragma region MenuExtention
|
||||||
|
void RegisterMenuExtensions();
|
||||||
|
void InitMenuExtension();
|
||||||
|
void CreateSubmenu(UToolMenu* Menu);
|
||||||
|
|
||||||
|
// void AddMenu(FMenuBarBuilder& MenuBarBuilder);
|
||||||
|
// void FillMenu(FMenuBuilder& MenuBuilder);
|
||||||
|
void OnButtonClicked();
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
#pragma region EditorTab
|
||||||
|
void RegisterRaceResultTab();
|
||||||
|
TSharedRef<SDockTab> OnSpawnTab(const FSpawnTabArgs& SpawnTabArgs);
|
||||||
|
private:
|
||||||
|
// static void DebugMenus();
|
||||||
|
static FName RaceResultTabId;
|
||||||
|
static FText RaceResultTabDisplayName;
|
||||||
|
TSharedPtr<class SDTFluxRaceResultWidget> RaceResultWidget;
|
||||||
|
#pragma endregion
|
||||||
|
};
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "SWebBrowser.h"
|
||||||
|
#include "Widgets/SCompoundWidget.h"
|
||||||
|
|
||||||
|
class IHttpResponse;
|
||||||
|
class IHttpRequest;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class DTFLUXRACERESULT_API SDTFluxRaceResultWidget : public SCompoundWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SLATE_BEGIN_ARGS(SDTFluxRaceResultWidget)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SLATE_END_ARGS()
|
||||||
|
|
||||||
|
/** Constructs this widget with InArgs */
|
||||||
|
void Construct(const FArguments& InArgs);
|
||||||
|
void OnCookieSet(bool bSuccess);
|
||||||
|
void LoadSpecificURL(const FString& Url);
|
||||||
|
void LoadContentViaHTTP();
|
||||||
|
|
||||||
|
private:
|
||||||
|
TSharedPtr<SWebBrowser> WebBrowser;
|
||||||
|
TSharedPtr<IWebBrowserWindow> Browser;
|
||||||
|
TSharedPtr<IWebBrowserAdapter> BrowserAdapter;
|
||||||
|
FCreateBrowserWindowSettings WindowSettings;
|
||||||
|
|
||||||
|
void OnUrlChanged(const FText& NewUrl);
|
||||||
|
FString ProcessHTMLContent(const FString& Content, const FString& BaseUrl);
|
||||||
|
FString ExtractDomain(const FString& Url);
|
||||||
|
void OnHTTPContentLoaded(TSharedPtr<IHttpRequest> Request, TSharedPtr<IHttpResponse> HttpResponse, bool bWasSuccessful);
|
||||||
|
bool OnBeforeNavigation(const FString& Url, const FWebNavigationRequest& Request);
|
||||||
|
void OnLoadCompleted();
|
||||||
|
void OnLoadStarted();
|
||||||
|
void OnLoadError();
|
||||||
|
|
||||||
|
void OnLoadOverride();
|
||||||
|
|
||||||
|
void OnBeforeResourceLoad(FString Url, FString ResourceType, FContextRequestHeaders& AdditionalHeaders, const bool AllowUserCredentials);
|
||||||
|
|
||||||
|
FString RaceResultUrl = "https://raceresult.tds-france.com";
|
||||||
|
FString Username = "sporkrono";
|
||||||
|
FString Password = "Notre 3ème décennie d'action pour le climat";
|
||||||
|
|
||||||
|
|
||||||
|
void SetupBasicAuth();
|
||||||
|
};
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Styling/ISlateStyle.h"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class DTFLUXRACERESULT_API FDTFluxRaceResultStyle
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void RegisterStyle();
|
||||||
|
static void UnregisterStyle();
|
||||||
|
|
||||||
|
static void ReloadTextures();
|
||||||
|
|
||||||
|
static const ISlateStyle& Get()
|
||||||
|
{
|
||||||
|
return *StyleSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const FName& GetStyleSetName()
|
||||||
|
{
|
||||||
|
return StyleSet->GetStyleSetName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static TSharedPtr<ISlateStyle> Create();
|
||||||
|
static TSharedPtr<ISlateStyle> StyleSet;
|
||||||
|
};
|
||||||
@ -19,7 +19,9 @@ public class DTFluxUtilities : ModuleRules
|
|||||||
"CoreUObject",
|
"CoreUObject",
|
||||||
"Engine",
|
"Engine",
|
||||||
"Slate",
|
"Slate",
|
||||||
"SlateCore"
|
"SlateCore",
|
||||||
|
"DTFluxCore",
|
||||||
|
"DTFluxCoreSubsystem",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
19
Source/DTFluxUtilities/Private/FTDFluxUtils.cpp
Normal file
19
Source/DTFluxUtilities/Private/FTDFluxUtils.cpp
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "FTDFluxUtils.h"
|
||||||
|
|
||||||
|
#include "DTFluxCoreSubsystem.h"
|
||||||
|
|
||||||
|
FText UFTDFluxUtils::GetFormatedName(const int& Bib, const int MaxChar, const FString OverFlowChar)
|
||||||
|
{
|
||||||
|
UDTFluxCoreSubsystem* CoreSubsystem = GEngine->GetEngineSubsystem<UDTFluxCoreSubsystem>();
|
||||||
|
const FDTFluxParticipant OutParticipant = CoreSubsystem->GetParticipant(Bib);
|
||||||
|
return OutParticipant.GetFormattedNameText(MaxChar, OverFlowChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
FText UFTDFluxUtils::GetParticipantFormatedName(FDTFluxParticipant& Participant, const int MaxChar,
|
||||||
|
const FString OverFlowChar)
|
||||||
|
{
|
||||||
|
return Participant.GetFormattedNameText(MaxChar, OverFlowChar);
|
||||||
|
}
|
||||||
25
Source/DTFluxUtilities/Public/FTDFluxUtils.h
Normal file
25
Source/DTFluxUtilities/Public/FTDFluxUtils.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "DTFluxCore/Public/Types/Struct/DTFluxTeamListStruct.h"
|
||||||
|
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||||
|
#include "FTDFluxUtils.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class DTFLUXUTILITIES_API UFTDFluxUtils : public UBlueprintFunctionLibrary
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="name, concat, participant"))
|
||||||
|
static FText GetFormatedName(const int& Bib, const int MaxChar = 10,
|
||||||
|
const FString OverFlowChar = "...");
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="name, concat, participant"))
|
||||||
|
static FText GetParticipantFormatedName(FDTFluxParticipant& Participant, const int MaxChar = 10,
|
||||||
|
const FString OverFlowChar = "...");
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user