Issue with lag when loading Datasmith files at runtime

I’d like to ask how you deal with the problem of lag when loading Datasmith files at runtime. When I use the load file node in the DatasmithRuntime plugin to load the scene, there will be lag. Then I put the logic in the background thread with C++ and tested it again, and there is still lag. After troubleshooting, I think the loading logic is indeed performed in the background thread, but the construction of the mesh and collision body and the update of the scene are still performed in the main thread. Does anyone know how to deal with this lag problem?
Can the effect of loading and displaying at the same time be achieved?
void ABlueprintTest::StatrAsyncEvent(ADatasmithRuntimeActor* DatasmithRuntimeActor, const FString& FileName)
{
AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, DatasmithRuntimeActor, FileName
{
bool bSuccess = LoadFile(DatasmithRuntimeActor, FileName);

    AsyncTask(ENamedThreads::GameThread, [bSuccess]()
    {
        if (bSuccess)
        {
            UKismetSystemLibrary::PrintString(nullptr, TEXT("File loaded successfully!"));
        }
        else
        {
            UKismetSystemLibrary::PrintString(nullptr, TEXT("Filed to load file!"));
        }
    });
});

}

I have preliminarily implemented the multithreading asynchronous loading of resources in the pak package file at runtime, but it is normal for it to run on the editing side, but after the program is packaged, the runtime always reports data conflict errors. Why? I have no problems debugging in the code. Can you give me some tips?


And attach the modified code that I have modified. Directly loading the Datasmith file requires the help of a plugin to parse the file, which can be difficult to modify. Therefore, I have temporarily changed the plan. I imported the Datasmith file into the engine and packaged it. I used Python to save the properties and hierarchical structure of the model in the scene in a JSON file. At runtime, I read the JSON file to restore the hierarchical structure and properties of the model
.h file

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "IPlatformFilePak.h"
#include "MyPlayerController.generated.h"

/**
 * 
 */

struct FModelPropertiesData
{
	FString Name;
	FVector Location;
	FRotator Rotation;
	FVector Scale;
	FString Type;
	FString Parent;
	FString MeshReference;
	TArray<FString> MaterialPaths;
};

UCLASS()
class DBJTEST_API AMyPlayerController : public APlayerController
{
	GENERATED_BODY()
	
public:
	virtual void BeginPlay() override;

	UFUNCTION(Exec, BlueprintCallable)
	void AsyncLoadPak(FString InPakFullPath);
	void StartSpawningAssets(TArray<FString> FoundFilenames);
	void SpawnNextAssets();
	void OnAssetLoaded(FString AssetName);
	
	TSharedPtr<class FPakPlatformFile> PakPlatform;
	class IPlatformFile* OldPlatform;

	void GetAllFilesInPak(FPakFile& PakFile, const FString& InPath, TArray<FString>& OutFiles);

	TArray<FModelPropertiesData> LoadActorDataFromJson(const FString& FilePath);

private:
	int32 SpawnIndex;
	int32 MeshIndex;
	TArray<UStaticMesh*> T_AssetStaticMeshes;
	FTimerHandle SpawnTimerHandle;

	TArray<FModelPropertiesData> T_ActorData;
	TMap<FString, UObject*> TM_MeshData;

	FCriticalSection MapMutex;
	TMap<FString, UStaticMesh*> TM_FileNames;
	TMap<FString, UMaterialInstance*> TM_MaterialNames;
	TArray<FString> FileNames;
	TArray<UStaticMesh*> AssetStaticMeshes;
	FTimerHandle AsyncSpawnTimerHandle;
	
};

.cpp file

#include "MyPlayerController.h"
#include "GameFramework/Actor.h"
#include "IPlatformFilePak.h"
#include "PlatformFilemanager.h"
#include "Runtime/Engine/Classes/Engine/StaticMeshActor.h"
#include "GenericPlatformFile.h"
#include "Chaos/Core.h"
#include "Engine/AssetManager.h"
#include "Engine/StreamableManager.h"

void AMyPlayerController::BeginPlay()
{
	Super::BeginPlay();

	OldPlatform = &FPlatformFileManager::Get().GetPlatformFile();
	PakPlatform = MakeShareable(new FPakPlatformFile());
	PakPlatform->Initialize(&FPlatformFileManager::Get().GetPlatformFile(), TEXT(""));
}

void AMyPlayerController::GetAllFilesInPak(FPakFile& PakFile, const FString& InPath, TArray<FString>& OutFiles)
{
	// Retrieve files in the current path
	TArray<FString> FoundFileNames;
	PakFile.FindPrunedFilesAtPath(FoundFileNames, *InPath, true, false, false);

	for (const FString& Filename : FoundFileNames)
	{
		// Add the current file to the list
		OutFiles.Add(Filename);
	}

	// Retrieve subdirectories under the current path
	TArray<FString> Subdirectories;
	PakFile.FindPrunedFilesAtPath(Subdirectories, *InPath, false, true, false);	//Retrieve directory path, do not retrieve files

	// Traverse subdirectories, recursively call GetAllFilesInPak
	for (const FString& Subdirectory : Subdirectories)
	{
		// Recursive call to retrieve all files in the subdirectories
		GetAllFilesInPak(PakFile, Subdirectory, OutFiles);
	}
}

void AMyPlayerController::AsyncLoadPak(FString InPakFullPath)
{
	AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, InPakFullPath]()
	{

		FString JsonFilePath = FPaths::ProjectDir() + TEXT("selected_objects250225.json");
		
		TArray<FModelPropertiesData> ActorData = LoadActorDataFromJson(JsonFilePath);	// Retrieve the JSON file of the hierarchical structure properties of scene objects
		
		TArray<UObject*> FoundObjects;
		FPlatformFileManager::Get().SetPlatformFile(*PakPlatform.Get());
	
		FString PakFileFullPath = InPakFullPath;
	
		TRefCountPtr<FPakFile> TmpPak = new FPakFile(PakPlatform.Get(), *PakFileFullPath, false);
		FString PakMountPoint = TmpPak->GetMountPoint();
		int32 Pos = PakMountPoint.Find("Content/");
		FString NewMountPoint = PakMountPoint.RightChop(Pos);
		NewMountPoint = FPaths::ProjectDir() + NewMountPoint;

		// Set mounting point
		TmpPak->SetMountPoint(*NewMountPoint);
		TArray<FString> FoundFilenames;
		GetAllFilesInPak(*TmpPak, *TmpPak->GetMountPoint(), FoundFilenames);

		TMap<FString, FString> TemporaryFiles;
		TMap<FString, FString> TM_FileNamesTest;
		
		for (const FString& Filename : FoundFilenames)
		{
			int32 LastPos = INDEX_NONE;
			int32 LastPos2 = INDEX_NONE;

			bool bFoundSlash = Filename.FindLastChar(TEXT('/'), LastPos);
			bool bFoundDot = Filename.FindLastChar(TEXT('.'), LastPos2);
			if (bFoundSlash && bFoundDot && Filename.EndsWith(TEXT(".uasset")))
			{
				TemporaryFiles.Add(Filename.Mid( LastPos + 1, LastPos2 - LastPos - 1), Filename);
			}
		}
		TArray<FString> TempTest;

		for (FModelPropertiesData& Data : ActorData)
		{
			if (TemporaryFiles.Contains(Data.Name))
			{
				TM_FileNamesTest.Add(Data.Name, TemporaryFiles[Data.Name]);
			}
			else
			{
				TM_FileNamesTest.Add(Data.Name, TEXT("None"));
			}

		}

		if (PakPlatform->Mount(*PakFileFullPath, 1, *NewMountPoint))
		{
			if (FoundFilenames.Num() > 0)
			{
				for (FString& Filename : FoundFilenames)
				{
					if (Filename.EndsWith(TEXT(".uasset")))
					{
						FString NewFileName = Filename;
						NewFileName.RemoveFromEnd(TEXT(".uasset"));
						int32 Pos = NewFileName.Find("/Content/");
						NewFileName = NewFileName.RightChop(Pos + 8);
						NewFileName = "/Game" + NewFileName;
						
						// Save the resource path to an array and load it asynchronously in the main thread later
						FoundObjects.AddUnique(FSoftObjectPath(NewFileName).ResolveObject());
					}
				}
				T_ActorData = ActorData;
			}
		}
		AsyncTask(ENamedThreads::GameThread, [this, FoundFilenames]()
		{
			StartSpawningAssets(FoundFilenames);
		});
	});
}

void AMyPlayerController::StartSpawningAssets(TArray<FString> FoundFilenames)
{
	// Use FStreamableManager Asynchronous loading of resources
	FStreamableManager& Streamable = UAssetManager::GetStreamableManager();
	
	for (FString& Filename : FoundFilenames)
	{
		if (Filename.EndsWith(TEXT(".uasset")))
		{
			FString AssetPath = Filename;
			AssetPath.RemoveFromEnd(TEXT(".uasset"));

			// replace "/Content/" to "/Game/"
			int32 Pos = AssetPath.Find(TEXT("/Content/"));
			if (Pos != INDEX_NONE)
			{
				AssetPath = "/Game" + AssetPath.RightChop(Pos + 8);	// 8is"/Content/length
			}

			// Handle possible double slashes "//"
			AssetPath = AssetPath.Replace(TEXT("//"), TEXT("/"));

			FSoftObjectPath AssetPathObject(AssetPath);
			UE_LOG(LogTemp, Log, TEXT("Requesting async load for: %s"), *AssetPath);

			Streamable.RequestAsyncLoad(AssetPathObject, FStreamableDelegate::CreateUObject(this, &AMyPlayerController::OnAssetLoaded, AssetPath));
		}
	}
}

void AMyPlayerController::OnAssetLoaded(FString AssetPath)
{
	UStaticMesh* LoadedMesh = Cast<UStaticMesh>(StaticLoadObject(UStaticMesh::StaticClass(), nullptr, *AssetPath));
	if (!LoadedMesh)
	{
		UE_LOG(LogTemp, Error, TEXT("Failed to load mesh from: %s"), *AssetPath);
		UMaterialInstance* Material = Cast<UMaterialInstance>(StaticLoadObject(UMaterialInstance::StaticClass(), nullptr, *AssetPath));
		if (!Material)
		{
			UE_LOG(LogTemp, Error, TEXT("Failed to load material from: %s"), *AssetPath);
		}
		MapMutex.Lock();
		TM_MaterialNames.Add(AssetPath, Material);
		MapMutex.Unlock();
		return;
	}
	UE_LOG(LogTemp, Log, TEXT("Successfully loaded mesh: %s"), *LoadedMesh->GetName());
	AssetStaticMeshes.Add(LoadedMesh);
	TM_FileNames.Add(LoadedMesh->GetName(), LoadedMesh);
	
	// Set a timer to generate 10 resources every 0.01 seconds
	GetWorld()->GetTimerManager().SetTimer(AsyncSpawnTimerHandle, this, &AMyPlayerController::SpawnNextAssets, 0.01f, true);

}

void AMyPlayerController::SpawnNextAssets()
{
	int32 NumToSpawn = 10;	// Generate 10 at a time

	for (int32 i = 0; i < NumToSpawn; i++)
	{
		if (SpawnIndex >= T_ActorData.Num())	// Generation completed
		{
			GetWorld()->GetTimerManager().ClearTimer(AsyncSpawnTimerHandle);
			
			UE_LOG(LogTemp, Log, TEXT("All asset spawned!"));
			FPlatformFileManager::Get().SetPlatformFile(*OldPlatform);
			return;
		}

		if (T_ActorData[SpawnIndex].Type.Contains(TEXT("Actor")))
		{
			if (T_ActorData[SpawnIndex].Type.Contains(TEXT("StaticMesh")))
			{
				AStaticMeshActor* StaticMeshActor = GetWorld()->SpawnActor<AStaticMeshActor>(AStaticMeshActor::StaticClass(), T_ActorData[SpawnIndex].Location, T_ActorData[SpawnIndex].Rotation);
				UStaticMesh* SM = TM_FileNames[T_ActorData[SpawnIndex].MeshReference];
				
				if (SM)
				{
					// StaticMeshActor->SetActorLabel(T_ActorData[SpawnIndex].Name);
					// StaticMeshActor->Rename(*T_ActorData[SpawnIndex].Name);
					StaticMeshActor->SetActorScale3D(T_ActorData[SpawnIndex].Scale);
					StaticMeshActor->SetMobility(EComponentMobility::Movable);
					if (T_ActorData[SpawnIndex].MaterialPaths.Num() > 0)
					{
						UMaterialInstance* Material = TM_MaterialNames[T_ActorData[SpawnIndex].MaterialPaths[0]];
						UStaticMeshComponent* SMC = StaticMeshActor->GetStaticMeshComponent();
						if (Material)
						{
							SMC->SetMaterial(0, Material);
						}
					}
					StaticMeshActor->GetStaticMeshComponent()->SetStaticMesh(SM);
				}
				if (T_ActorData[SpawnIndex].Parent != "None")
				{
					AActor* ParentActor = Cast<AActor>(TM_MeshData[T_ActorData[SpawnIndex].Parent]);
					if (ParentActor && ParentActor->IsValidLowLevel())
					{
						StaticMeshActor->AttachToActor(ParentActor, FAttachmentTransformRules::KeepRelativeTransform);
						StaticMeshActor->SetMobility(EComponentMobility::Static);
					}
				}
				TM_MeshData.Add(T_ActorData[SpawnIndex].Name, StaticMeshActor);
			}
			else
			{
				// Dynamically create actors
				FActorSpawnParameters SpawnParameters;
				AActor* Actor = GetWorld()->SpawnActor<AActor>(AActor::StaticClass(), T_ActorData[SpawnIndex].Location, T_ActorData[SpawnIndex].Rotation, SpawnParameters);
				if (Actor)
				{
					// Add SceneComponent as the root component for ParentActor
					USceneComponent* RootComponent = NewObject<USceneComponent>(Actor, TEXT("RootComponent"));
					Actor->SetRootComponent(RootComponent);
					Actor->SetActorScale3D(T_ActorData[SpawnIndex].Scale);
					RootComponent->RegisterComponent();
					// Actor->Rename(*T_ActorData[SpawnIndex].Name);
					// Actor->SetActorLabel(T_ActorData[SpawnIndex].Name);
					
					if (T_ActorData[SpawnIndex].Parent != "None")
					{
						AActor* ParentActor = Cast<AActor>(TM_MeshData[T_ActorData[SpawnIndex].Parent]);
						if (ParentActor && ParentActor->IsValidLowLevel())
						{
							Actor->AttachToActor(ParentActor, FAttachmentTransformRules::KeepRelativeTransform);
						}
					}
					TM_MeshData.Add(T_ActorData[SpawnIndex].Name, Actor);
				}
			}
		}
		SpawnIndex++;
	}
}

TArray<FModelPropertiesData> AMyPlayerController::LoadActorDataFromJson(const FString& FilePath)
{
	TArray<FModelPropertiesData> ActorArray;

	FString JsonStr;
	if (!FFileHelper::LoadFileToString(JsonStr, *FilePath))
	{
		UE_LOG(LogTemp, Error, TEXT("Failed to load JSON file: %s"), *FilePath);
		return ActorArray;
	}

	TSharedPtr<FJsonValue> JsonParsed;
	TSharedRef<TJsonReader<TCHAR>> Reader = TJsonReaderFactory<TCHAR>::Create(JsonStr);
	if (!FJsonSerializer::Deserialize(Reader, JsonParsed) || !JsonParsed.IsValid())
	{
		UE_LOG(LogTemp, Error, TEXT("Failed to parse JSON file"));
		return ActorArray;
	}

	TArray<TSharedPtr<FJsonValue>> JsonArray = JsonParsed->AsArray();
	for (TSharedPtr<FJsonValue> Value : JsonArray)
	{
		TSharedPtr<FJsonObject> JsonObject = Value->AsObject();
		if (!JsonObject.IsValid()) continue;

		FModelPropertiesData ActorData;

		ActorData.Name = JsonObject->GetStringField("name");

		TSharedPtr<FJsonObject> Loc = JsonObject->GetObjectField("location");
		ActorData.Location = FVector(Loc->GetNumberField("x"), Loc->GetNumberField("y"), Loc->GetNumberField("z"));

		TSharedPtr<FJsonObject> Rot = JsonObject->GetObjectField("rotation");
		FRotator Rotator;
		// The rotation value order in Unreal is different from the Euler angle representation obtained in Python and needs to be swapped (the rotation value of an object can also be obtained and saved using quaternions, which are also used for assigning values in Unreal)
		Rotator.Pitch = Rot->GetNumberField("yaw");
		Rotator.Roll = Rot->GetNumberField("pitch");
		Rotator.Yaw = Rot->GetNumberField("roll");
		ActorData.Rotation = Rotator;
		
		TSharedPtr<FJsonObject> Sc = JsonObject->GetObjectField("scale");
		ActorData.Scale = FVector(Sc->GetNumberField("x"), Sc->GetNumberField("y"), Sc->GetNumberField("z"));

		ActorData.Type = JsonObject->GetStringField("type");
		ActorData.Parent = JsonObject->GetStringField("parent");

		if (!JsonObject->GetStringField("static_mesh_asset").IsEmpty())
		{
			ActorData.MeshReference = JsonObject->GetStringField("static_mesh_asset");
		}
		else
		{
			ActorData.MeshReference = "None";
		}

		// Parse the static_mesh_materials array
		if (JsonObject->HasField("static_mesh_materials"))
		{
			TArray<TSharedPtr<FJsonValue>>MaterialsArray = JsonObject->GetArrayField("static_mesh_materials");
			for (TSharedPtr<FJsonValue> MaterialsValue : MaterialsArray)
			{
				FString MaterialPath = MaterialsValue->AsString();
				int32 PosIndex = INDEX_NONE;
				bool bVaild = MaterialPath.FindLastChar('.', PosIndex);
				if (bVaild)
				{
					MaterialPath = MaterialPath.Mid(0, PosIndex);
				}
				ActorData.MaterialPaths.Add(MaterialPath);
			}
		}
		
		ActorArray.Add(ActorData);
	}

	UE_LOG(LogTemp, Log, TEXT("Successfully parsed %d actors from JSON"), ActorArray.Num());
	return ActorArray;
}
type or paste code here