diff --git a/UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/Hdf5DataPlugin/Private/Hdf5SimulationReader.cpp b/UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/Hdf5DataPlugin/Private/Hdf5SimulationReader.cpp index 5acbaece0..96c3333a3 100644 --- a/UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/Hdf5DataPlugin/Private/Hdf5SimulationReader.cpp +++ b/UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/Hdf5DataPlugin/Private/Hdf5SimulationReader.cpp @@ -23,9 +23,26 @@ #include "Hdf5SimulationReader.h" #include "Misc/Paths.h" +#include "Misc/ScopeLock.h" DEFINE_LOG_CATEGORY_STATIC(LogHdf5SimulationReader, Log, All); +namespace +{ + /** + * The bundled HDF5 library is built without thread safety (Threadsafety: OFF in + * libhdf5.settings), so no two threads may execute HDF5 code concurrently. + * Geometry and trajectory loading both read the same .h5 from different threads, + * so every public reader method serializes on this process-wide lock. + * The lock is recursive on all UE platforms, so public methods may call each other. + */ + FCriticalSection& GetHdf5LibraryLock() + { + static FCriticalSection Hdf5LibraryLock; + return Hdf5LibraryLock; + } +} + FHdf5SimulationReader::FHdf5SimulationReader() { } @@ -37,6 +54,8 @@ FHdf5SimulationReader::~FHdf5SimulationReader() bool FHdf5SimulationReader::OpenFile(const FString& FilePath) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + // Close any previously open file CloseFile(); @@ -58,7 +77,8 @@ bool FHdf5SimulationReader::OpenFile(const FString& FilePath) if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("Failed to open HDF5 file: %s"), *FilePath); - H5close(); + // Do not call H5close() here: it tears down the HDF5 library process-wide, + // crashing any other reader that is mid-operation. return false; } @@ -111,10 +131,13 @@ bool FHdf5SimulationReader::OpenFile(const FString& FilePath) void FHdf5SimulationReader::CloseFile() { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId >= 0) { H5Fclose(FileId); - H5close(); + // Deliberately no H5close(): that shuts down the HDF5 library for the whole + // process, not just this file, and other readers may still be active. FileId = -1; DetectedFormat = EHdf5FormatType::Unknown; TimestepCount = 0; @@ -276,6 +299,8 @@ bool FHdf5SimulationReader::ReadStringAttribute(hid_t GroupId, const char* AttrN bool FHdf5SimulationReader::ReadMetadata(FHdf5SimulationMetadata& OutMetadata) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("No file is open")); @@ -320,6 +345,8 @@ bool FHdf5SimulationReader::ReadMetadata(FHdf5SimulationMetadata& OutMetadata) bool FHdf5SimulationReader::ReadEntities(TArray& OutEntities) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("No file is open")); @@ -417,6 +444,8 @@ bool FHdf5SimulationReader::ReadEntities(TArray& OutEntities) bool FHdf5SimulationReader::ReadTimesteps(TArray& OutTimesteps) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("No file is open")); @@ -453,6 +482,8 @@ bool FHdf5SimulationReader::ReadTimesteps(TArray& OutTimesteps) bool FHdf5SimulationReader::ReadSamplesPerTimestep(TArray& OutSamplesPerTimestep) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("No file is open")); @@ -496,6 +527,8 @@ bool FHdf5SimulationReader::ReadSamplesPerTimestep(TArray& OutSamplesPerT bool FHdf5SimulationReader::ReadAllSamples(TArray& OutSamples, bool* OutHasRotationField, bool* OutHasSpeedField) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("No file is open")); @@ -608,6 +641,8 @@ bool FHdf5SimulationReader::ReadAllSamples(TArray& OutSamples, bool FHdf5SimulationReader::ReadSamplesForTimestepRange(int32 StartTimestep, int32 EndTimestep, TArray& OutSamples) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("No file is open")); @@ -718,6 +753,8 @@ EHdf5FormatType FHdf5SimulationReader::DetectFormat(const FString& FilePath) return EHdf5FormatType::Unknown; } + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + FTCHARToUTF8 FilePathUtf8(*FilePath); // Check if it's a valid HDF5 file @@ -731,7 +768,6 @@ EHdf5FormatType FHdf5SimulationReader::DetectFormat(const FString& FilePath) hid_t TempFileId = H5Fopen(FilePathUtf8.Get(), H5F_ACC_RDONLY, H5P_DEFAULT); if (TempFileId < 0) { - H5close(); return EHdf5FormatType::Unknown; } @@ -750,7 +786,6 @@ EHdf5FormatType FHdf5SimulationReader::DetectFormat(const FString& FilePath) } H5Fclose(TempFileId); - H5close(); return Result; } @@ -759,6 +794,8 @@ EHdf5FormatType FHdf5SimulationReader::DetectFormat(const FString& FilePath) bool FHdf5SimulationReader::ReadJuelichMetadata(FHdf5JuelichMetadata& OutMetadata) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("No file is open")); @@ -845,6 +882,8 @@ bool FHdf5SimulationReader::ReadJuelichMetadata(FHdf5JuelichMetadata& OutMetadat bool FHdf5SimulationReader::ReadJuelichTrajectories(TArray& OutRecords) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("No file is open")); @@ -924,6 +963,8 @@ bool FHdf5SimulationReader::ReadJuelichTrajectories(TArraySetSimulationMeshFilePath(DataPath); + GameInst->SetSimulationMeshFileName(FPaths::GetCleanFilename(DataPath)); + } +} + UProjectMobiusGameInstance* IProjectMobiusInterface::GetMobiusGameInstance(UWorld* World) { if(!World) @@ -111,6 +150,9 @@ void IProjectMobiusInterface::UpdateMobiusGameInstancePedestrianData(UWorld* Wor // Set the pedestrian data file name MobiusGameInst->SetPedestrianDataFileName(FPaths::GetCleanFilename(CompleteDataPath)); + + // HDF5 files can embed the geometry, load it from the same file + AdoptEmbeddedGeometrySource(MobiusGameInst, CompleteDataPath); } void IProjectMobiusInterface::GetMobiusGameInstanceMeshDataFile(UWorld* World, FString& OutCompleteDataPath, diff --git a/UnrealFolder/ProjectMobius/Source/MobiusWidgets/Private/UI/LoadSave/LoadAgentDataWidget.cpp b/UnrealFolder/ProjectMobius/Source/MobiusWidgets/Private/UI/LoadSave/LoadAgentDataWidget.cpp index b10b0056b..9fb947f80 100644 --- a/UnrealFolder/ProjectMobius/Source/MobiusWidgets/Private/UI/LoadSave/LoadAgentDataWidget.cpp +++ b/UnrealFolder/ProjectMobius/Source/MobiusWidgets/Private/UI/LoadSave/LoadAgentDataWidget.cpp @@ -132,7 +132,7 @@ void ULoadAgentDataWidget::DialogClosed(const FString& AgentFilePath, const FStr Feedback->ReportError( FText::FromString("Invalid Agent Data File"), FText::FromString("Unsupported agent data file type selected."), - FText::FromString("Supported types: .json"), + FText::FromString("Supported types: .json, .h5"), FText::FromString("Load Agent Data")); } UE_LOG(LogTemp, Warning, TEXT("The file dialog was canceled or an error occurred")); diff --git a/UnrealFolder/ProjectMobius/Source/MobiusWidgets/Private/UI/LoadSave/LoadMeshWidget.cpp b/UnrealFolder/ProjectMobius/Source/MobiusWidgets/Private/UI/LoadSave/LoadMeshWidget.cpp index ce069e21d..b3218b7a6 100644 --- a/UnrealFolder/ProjectMobius/Source/MobiusWidgets/Private/UI/LoadSave/LoadMeshWidget.cpp +++ b/UnrealFolder/ProjectMobius/Source/MobiusWidgets/Private/UI/LoadSave/LoadMeshWidget.cpp @@ -25,11 +25,33 @@ #include "UI/LoadSave/LoadMeshWidget.h" #include "Subsystems/NativeFileDialogSubsystem.h" #include "Subsystems/MobiusUserFeedbackSubsystem.h" +#include "GameInstances/ProjectMobiusGameInstance.h" void ULoadMeshWidget::NativeConstruct() { Super::NativeConstruct(); + if (UProjectMobiusGameInstance* MobiusGameInst = IProjectMobiusInterface::GetMobiusGameInstance(GetWorld())) + { + MobiusGameInst->OnMeshFileChanged.AddUniqueDynamic(this, &ULoadMeshWidget::OnGameInstanceMeshFileChanged); + } +} + +void ULoadMeshWidget::NativeDestruct() +{ + if (UProjectMobiusGameInstance* MobiusGameInst = IProjectMobiusInterface::GetMobiusGameInstance(GetWorld())) + { + MobiusGameInst->OnMeshFileChanged.RemoveDynamic(this, &ULoadMeshWidget::OnGameInstanceMeshFileChanged); + } + + Super::NativeDestruct(); +} + +void ULoadMeshWidget::OnGameInstanceMeshFileChanged() +{ + // Pull the new path out of the game instance and refresh the text block + GetMobiusGameInstanceData(); + UpdateFileTextBlockTexts(); } void ULoadMeshWidget::OnSelectFileButtonClicked() @@ -132,7 +154,7 @@ void ULoadMeshWidget::DialogClosed(const FString& AgentFilePath, const FString& Feedback->ReportError( FText::FromString("Invalid Mesh File"), FText::FromString("Unsupported mesh file type selected."), - FText::FromString("Supported types: .fbx, .obj, .udatasmith, .ifc, .wkt"), + FText::FromString("Supported types: .fbx, .obj, .udatasmith, .ifc, .wkt, .h5"), FText::FromString("Load Mesh")); } UE_LOG(LogTemp, Warning, TEXT("The file dialog was canceled or an error occurred")); diff --git a/UnrealFolder/ProjectMobius/Source/MobiusWidgets/Public/UI/LoadSave/LoadMeshWidget.h b/UnrealFolder/ProjectMobius/Source/MobiusWidgets/Public/UI/LoadSave/LoadMeshWidget.h index e00fafa83..7c9e35d83 100644 --- a/UnrealFolder/ProjectMobius/Source/MobiusWidgets/Public/UI/LoadSave/LoadMeshWidget.h +++ b/UnrealFolder/ProjectMobius/Source/MobiusWidgets/Public/UI/LoadSave/LoadMeshWidget.h @@ -41,6 +41,8 @@ class MOBIUSWIDGETS_API ULoadMeshWidget : public ULoadDataParentWidget #pragma region PUBLIC_METHODS // Constructor virtual void NativeConstruct() override; + + virtual void NativeDestruct() override; /** * Method to call when the SelectFileButton is clicked @@ -66,6 +68,13 @@ class MOBIUSWIDGETS_API ULoadMeshWidget : public ULoadDataParentWidget /** Handler for file dialog errors. Displays error popup to user. */ UFUNCTION() void OnDialogError(const FString& ErrorTitle, const FString& ErrorMessage); + + /** + * Refresh the displayed path when the geometry file is changed elsewhere, e.g. when an + * HDF5 pedestrian data file supplies its own embedded geometry. + */ + UFUNCTION() + void OnGameInstanceMeshFileChanged(); #pragma endregion PUBLIC_METHODS #pragma endregion METHODS