Files
2025-05-18 13:04:45 +08:00

1393 lines
43 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SceneImporter.h"
#include "DatasmithRuntimeUtils.h"
#include "LogCategory.h"
#include "MaterialImportUtils.h"
#include "DatasmithAssetUserData.h"
#include "DatasmithNativeTranslator.h"
#include "DatasmithMaterialElements.h"
#include "Async/Async.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Misc/Paths.h"
#include "RenderingThread.h"
#include "Misc/Paths.h"
namespace DatasmithRuntime
{
extern void UpdateMaterials(TSet<FSceneGraphId>& MaterialElementSet, TMap< FSceneGraphId, FAssetData >& AssetDataList);
extern void HideSceneComponent(USceneComponent* SceneComponent);
#ifdef LIVEUPDATE_TIME_LOGGING
Timer::Timer(double InTimeOrigin, const char* InText)
: TimeOrigin(InTimeOrigin)
, StartTime(FPlatformTime::Seconds())
, Text(InText)
{
}
Timer::~Timer()
{
const double EndTime = FPlatformTime::Seconds();
const double ElapsedMilliSeconds = (EndTime - StartTime) * 1000.;
double SecondsSinceOrigin = EndTime - TimeOrigin;
const int MinSinceOrigin = int(SecondsSinceOrigin / 60.);
SecondsSinceOrigin -= 60.0 * (double)MinSinceOrigin;
UE_LOG(LogDatasmithRuntime, Log, TEXT("%s in [%.3f ms] ( since beginning [%d min %.3f s] )"), *Text, ElapsedMilliSeconds, MinSinceOrigin, SecondsSinceOrigin);
}
#endif
const FString TexturePrefix( TEXT( "Texture." ) );
const FString MaterialPrefix( TEXT( "Material." ) );
const FString MeshPrefix( TEXT( "Mesh." ) );
FAssetData FAssetData::EmptyAsset(DirectLink::InvalidId);
FSceneImporter::FSceneImporter(ADatasmithRuntimeActor* InDatasmithRuntimeActor)
: RootComponent( InDatasmithRuntimeActor->GetRootComponent() )
, TasksToComplete( EWorkerTask::NoTask )
, OverallProgress(InDatasmithRuntimeActor->Progress)
{
SceneKey = GetTypeHash(FGuid::NewGuid());
FAssetRegistry::RegisterMapping(SceneKey, &AssetDataList);
FAssetData::EmptyAsset.SetState(EAssetState::Processed | EAssetState::Completed);
}
FSceneImporter::~FSceneImporter()
{
DeleteData();
FAssetRegistry::UnregisterMapping(SceneKey);
}
TStatId FSceneImporter::GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FSceneImporter, STATGROUP_Tickables);
}
void FSceneImporter::ParseScene( const TSharedPtr<IDatasmithActorElement>& ActorElement, FSceneGraphId ParentId, FParsingCallback Callback )
{
Callback( ActorElement, ParentId );
FSceneGraphId ActorId = ActorElement->GetNodeId();
for (int32 Index = 0; Index < ActorElement->GetChildrenCount(); ++Index)
{
ParseScene( ActorElement->GetChild(Index), ActorId, Callback );
}
}
void FSceneImporter::StartImport(TSharedRef<IDatasmithScene> InSceneElement, const FDatasmithRuntimeImportOptions& Options)
{
Reset(true);
ImportOptions = Options;
SceneElement = InSceneElement;
if (SceneElement.IsValid() && !Translator.IsValid())
{
Translator = MakeShared<FDatasmithNativeTranslator>();
}
TasksToComplete |= SceneElement.IsValid() ? EWorkerTask::CollectSceneData : EWorkerTask::NoTask;
#ifdef LIVEUPDATE_TIME_LOGGING
GlobalStartTime = FPlatformTime::Seconds();
#endif
}
void FSceneImporter::AddAsset(TSharedPtr<IDatasmithElement>&& InElementPtr, const FString& AssetPrefix, EDataType DataType)
{
if (IDatasmithElement* Element = InElementPtr.Get())
{
const FString AssetKey = AssetPrefix + Element->GetName();
const FSceneGraphId ElementId = Element->GetNodeId();
AssetElementMapping.Add( AssetKey, ElementId );
Elements.Add( ElementId, MoveTemp( InElementPtr ) );
FAssetData AssetData(ElementId, DataType);
AssetDataList.Emplace(ElementId, MoveTemp(AssetData));
}
}
void FSceneImporter::CollectSceneData()
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSceneImporter::CollectSceneData);
LIVEUPDATE_LOG_TIME;
int32 ActorElementCount = 0;
for (int32 Index = 0; Index < SceneElement->GetActorsCount(); ++Index)
{
ParseScene( SceneElement->GetActor(Index), DirectLink::InvalidId,
[&](const TSharedPtr< IDatasmithActorElement>& ActorElement, FSceneGraphId ActorId) -> void
{
++ActorElementCount;
});
}
int32 AssetElementCount = SceneElement->GetTexturesCount() + SceneElement->GetMaterialsCount() +
SceneElement->GetMeshesCount() + SceneElement->GetLevelSequencesCount();
if (bIncrementalUpdate)
{
// Make sure to pre-allocate enough memory as pointer on values in those maps are used
TextureDataList.Reserve( FMath::Max(TextureDataList.Num(), SceneElement->GetTexturesCount()) );
AssetDataList.Reserve( FMath::Max(AssetDataList.Num(), AssetElementCount) );
ActorDataList.Reserve( FMath::Max(ActorDataList.Num(), ActorElementCount) );
Elements.Reserve( FMath::Max(Elements.Num(), AssetElementCount + ActorElementCount) );
DependencyList.Reserve(FMath::Max(DependencyList.Num(), SceneElement->GetMeshesCount() + SceneElement->GetMetaDataCount()));
AssetElementMapping.Reserve( FMath::Max(AssetElementMapping.Num(), AssetElementCount) );
// Reset counters
QueuedTaskCount = 0;
// Parse scene to collect all actions to be taken
for (int32 Index = 0; Index < SceneElement->GetActorsCount(); ++Index)
{
ParseScene(SceneElement->GetActor(Index), DirectLink::InvalidId,
[this](const TSharedPtr<IDatasmithActorElement>& ActorElement, FSceneGraphId ParentId) -> void
{
this->ProcessActorElement(ActorElement, ParentId);
}
);
}
// #ue_datasmithruntime: What about lightmap weights on incremental update?
LightmapWeights.Empty();
bIncrementalUpdate = false;
}
else
{
// Make sure to pre-allocate enough memory as pointer on values in those maps are used
TextureDataList.Empty( SceneElement->GetTexturesCount() );
AssetDataList.Empty( AssetElementCount );
ActorDataList.Empty( ActorElementCount );
Elements.Empty( AssetElementCount + ActorElementCount );
DependencyList.Empty(SceneElement->GetMeshesCount());
AssetElementMapping.Empty( AssetElementCount );
for (int32 Index = 0; Index < SceneElement->GetTexturesCount(); ++Index)
{
// Only add a texture if its associated resource file is available
if (IDatasmithTextureElement* TextureElement = static_cast<IDatasmithTextureElement*>(SceneElement->GetTexture(Index).Get()))
{
// If resource file does not exist, add scene's resource path if valid
if (!FPaths::FileExists(TextureElement->GetFile()) && FPaths::DirectoryExists(SceneElement->GetResourcePath()))
{
TextureElement->SetFile( *FPaths::Combine(SceneElement->GetResourcePath(), TextureElement->GetFile()) );
}
if (FPaths::FileExists(TextureElement->GetFile()))
{
AddAsset(SceneElement->GetTexture(Index), TexturePrefix, EDataType::Texture);
}
else
{
EDatasmithTextureFormat TextureFormat;
uint32 ByteCount;
if (TextureElement->GetData(ByteCount, TextureFormat) != nullptr && ByteCount > 0)
{
AddAsset(SceneElement->GetTexture(Index), TexturePrefix, EDataType::Texture);
}
}
}
// #ueent_datasmithruntime: Inform user resource file does not exist
}
for (int32 Index = 0; Index < SceneElement->GetMaterialsCount(); ++Index)
{
AddAsset(SceneElement->GetMaterial(Index), MaterialPrefix, EDataType::Material);
}
for (int32 Index = 0; Index < SceneElement->GetMeshesCount(); ++Index)
{
// Only add a mesh if its associated resource is available
if (IDatasmithMeshElement* MeshElement = static_cast<IDatasmithMeshElement*>(SceneElement->GetMesh(Index).Get()))
{
AddAsset(SceneElement->GetMesh(Index), MeshPrefix, EDataType::Mesh);
}
// #ueent_datasmithruntime: Inform user resource file does not exist
}
// Collect set of materials and meshes used in scene
// Collect set of textures used in scene
TextureElementSet.Empty(SceneElement->GetTexturesCount());
MeshElementSet.Empty(SceneElement->GetMeshesCount());
MaterialElementSet.Empty(SceneElement->GetMaterialsCount());
for (int32 Index = 0; Index < SceneElement->GetActorsCount(); ++Index)
{
ParseScene(SceneElement->GetActor(Index), DirectLink::InvalidId,
[this](const TSharedPtr<IDatasmithActorElement>& ActorElement, FSceneGraphId ParentId) -> void
{
this->ProcessActorElement(ActorElement, ParentId);
}
);
}
if (ImportOptions.bImportMetaData)
{
// Start collection of metadata
MetadataCollect = Async(
#if WITH_EDITOR
EAsyncExecution::LargeThreadPool,
#else
EAsyncExecution::ThreadPool,
#endif
[this]() -> void
{
for (int32 Index = 0; Index < this->SceneElement->GetMetaDataCount(); ++Index)
{
this->ProcessMetdata(this->SceneElement->GetMetaData(Index));
}
}
);
}
}
TasksToComplete |= EWorkerTask::SetupTasks;
}
void FSceneImporter::SetupTasks()
{
LIVEUPDATE_LOG_TIME;
// Compute parameters for update on progress
int32 ActionsCount = QueuedTaskCount;
ActionsCount += MaterialElementSet.Num();
if (TextureElementSet.Num() > 0)
{
ImageReaderInitialize();
TasksToComplete |= EWorkerTask::TextureLoad;
}
// Add image load + texture creation + texture assignments
for (FSceneGraphId ElementId : TextureElementSet)
{
const FAssetData& AssetData = AssetDataList[ElementId];
ActionsCount += AssetData.Referencers.Num() + 2;
}
OverallProgress = 0.05f;
double MaxActions = FMath::FloorToDouble( (double)ActionsCount / 0.95 );
ActionCounter.Set((int32)FMath::CeilToDouble( MaxActions * 0.05 ));
ProgressStep = 1. / MaxActions;
OnGoingTasks.Reserve(TextureElementSet.Num() + MeshElementSet.Num());
}
void FSceneImporter::Tick(float DeltaSeconds)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSceneImporter::Tick);
if (TasksToComplete == EWorkerTask::NoTask)
{
return;
}
// Full reset of the world. Resume tasks on next tick
if (EnumHasAnyFlags( TasksToComplete, EWorkerTask::ResetScene))
{
// Wait for ongoing tasks to be completed
for (TFuture<bool>& OnGoingTask : OnGoingTasks)
{
OnGoingTask.Wait();
}
OnGoingTasks.Empty();
bool bGarbageCollect = DeleteData();
Elements.Empty();
AssetElementMapping.Empty();
AssetDataList.Empty();
TextureDataList.Empty();
ActorDataList.Empty();
DependencyList.Empty();
bGarbageCollect |= FAssetRegistry::CleanUp();
TasksToComplete &= ~EWorkerTask::ResetScene;
// If there is no more tasks to complete, delete assets which are not used
if (bGarbageCollect)
{
if (!IsGarbageCollecting())
{
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
}
else
{
// Post-pone garbage collection for next frame
TasksToComplete = EWorkerTask::GarbageCollect;
}
}
return;
}
struct FLocalUpdate
{
FLocalUpdate(float& InProgress, FThreadSafeCounter& InCounter, double InStep)
: Progress(InProgress)
, Counter(InCounter)
, Step(InStep)
{
}
~FLocalUpdate()
{
Progress = (float)((double)Counter.GetValue() * Step);
}
float& Progress;
FThreadSafeCounter& Counter;
double Step;
};
FLocalUpdate LocalUpdate(OverallProgress, ActionCounter, ProgressStep);
// Execute work by chunk of 10 milliseconds timespan
double EndTime = FPlatformTime::Seconds() + 0.02;
if (EnumHasAnyFlags( TasksToComplete, EWorkerTask::GarbageCollect))
{
// Do not take any risk, wait for next frame to continue the process
if (IsGarbageCollecting())
{
return;
}
#ifdef LIVEUPDATE_TIME_LOGGING
Timer(GlobalStartTime, "GarbageCollect");
#endif
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
TasksToComplete &= ~EWorkerTask::GarbageCollect;
}
bool bContinue = FPlatformTime::Seconds() < EndTime;
if (bContinue && EnumHasAnyFlags(TasksToComplete, EWorkerTask::DeleteComponent))
{
#ifdef LIVEUPDATE_TIME_LOGGING
Timer __Timer(GlobalStartTime, "DeleteComponent");
#endif
FActionTask ActionTask;
while (FPlatformTime::Seconds() < EndTime)
{
if (!ActionQueues[EQueueTask::DeleteCompQueue].Dequeue(ActionTask))
{
TasksToComplete &= ~EWorkerTask::DeleteComponent;
break;
}
ActionTask.Execute(FAssetData::EmptyAsset);
}
}
bContinue = FPlatformTime::Seconds() < EndTime;
// Do not continue if there are still components to garbage collect
// Force a garbage collection if we are done with the components
if (bContinue && EnumHasAnyFlags(TasksToComplete, EWorkerTask::GarbageCollect))
{
// Terminate all rendering commands before deleting any component
FlushRenderingCommands();
if (IsGarbageCollecting())
{
return;
}
#ifdef LIVEUPDATE_TIME_LOGGING
Timer __Timer(GlobalStartTime, "GarbageCollect");
#endif
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
TasksToComplete &= ~EWorkerTask::GarbageCollect;
}
bContinue = FPlatformTime::Seconds() < EndTime;
if (bContinue && EnumHasAnyFlags(TasksToComplete, EWorkerTask::DeleteAsset))
{
#ifdef LIVEUPDATE_TIME_LOGGING
Timer __Timer(GlobalStartTime, "DeleteAsset");
#endif
FActionTask ActionTask;
while (FPlatformTime::Seconds() < EndTime)
{
if (!ActionQueues[EQueueTask::DeleteAssetQueue].Dequeue(ActionTask))
{
TasksToComplete &= ~EWorkerTask::DeleteAsset;
break;
}
ActionTask.Execute(FAssetData::EmptyAsset);
}
}
bContinue = FPlatformTime::Seconds() < EndTime;
if (bContinue && EnumHasAnyFlags(TasksToComplete, EWorkerTask::CollectSceneData))
{
CollectSceneData();
TasksToComplete &= ~EWorkerTask::CollectSceneData;
}
bContinue = FPlatformTime::Seconds() < EndTime;
if (bContinue && EnumHasAnyFlags(TasksToComplete, EWorkerTask::UpdateElement))
{
#ifdef LIVEUPDATE_TIME_LOGGING
Timer __Timer(GlobalStartTime, "UpdateElement");
#endif
ProcessQueue(EQueueTask::UpdateQueue, EndTime, EWorkerTask::UpdateElement, EWorkerTask::SetupTasks);
}
bContinue = FPlatformTime::Seconds() < EndTime;
if (bContinue && EnumHasAnyFlags( TasksToComplete, EWorkerTask::SetupTasks))
{
SetupTasks();
TasksToComplete &= ~EWorkerTask::SetupTasks;
}
// Do not proceed further if metadata collection is not complete
if (MetadataCollect.IsValid() && !MetadataCollect.IsReady())
{
return;
}
bContinue = FPlatformTime::Seconds() < EndTime;
if (bContinue && EnumHasAnyFlags(TasksToComplete, EWorkerTask::MeshCreate))
{
#ifdef LIVEUPDATE_TIME_LOGGING
Timer __Timer(GlobalStartTime, "MeshCreate");
#endif
ProcessQueue(EQueueTask::MeshQueue, EndTime, EWorkerTask::MeshCreate);
}
bContinue = FPlatformTime::Seconds() < EndTime;
if (bContinue && EnumHasAnyFlags(TasksToComplete, EWorkerTask::MaterialCreate))
{
#ifdef LIVEUPDATE_TIME_LOGGING
Timer __Timer(GlobalStartTime, "MaterialCreate");
#endif
FActionTask ActionTask;
while (FPlatformTime::Seconds() < EndTime)
{
if (!ActionQueues[EQueueTask::MaterialQueue].Dequeue(ActionTask))
{
TasksToComplete &= ~EWorkerTask::MaterialCreate;
if (!EnumHasAnyFlags(TasksToComplete, EWorkerTask::TextureAssign))
{
UpdateMaterials(MaterialElementSet, AssetDataList);
}
break;
}
ensure(DirectLink::InvalidId == ActionTask.GetElementId());
ActionTask.Execute(FAssetData::EmptyAsset);
}
}
bContinue = FPlatformTime::Seconds() < EndTime;
if (bContinue && EnumHasAnyFlags(TasksToComplete, EWorkerTask::TextureLoad))
{
#ifdef LIVEUPDATE_TIME_LOGGING
Timer __Timer(GlobalStartTime, "TextureLoad");
#endif
ProcessQueue(EQueueTask::TextureQueue, EndTime, EWorkerTask::TextureLoad);
}
bContinue = FPlatformTime::Seconds() < EndTime;
if (bContinue && EnumHasAnyFlags(TasksToComplete, EWorkerTask::NonAsyncTasks))
{
#ifdef LIVEUPDATE_TIME_LOGGING
Timer __Timer(GlobalStartTime, "GameThreadTasks");
#endif
FActionTask ActionTask;
while (FPlatformTime::Seconds() < EndTime)
{
if (!ActionQueues[EQueueTask::NonAsyncQueue].Dequeue(ActionTask))
{
if (EnumHasAnyFlags(TasksToComplete, EWorkerTask::TextureAssign))
{
UpdateMaterials(MaterialElementSet, AssetDataList);
}
TasksToComplete &= ~EWorkerTask::NonAsyncTasks;
break;
}
const FSceneGraphId ElementId = ActionTask.GetElementId();
FBaseData& ElementData = DirectLink::InvalidId == ElementId ? FAssetData::EmptyAsset : (AssetDataList.Contains(ElementId) ? (FBaseData&)AssetDataList[ElementId] : (FBaseData&)ActorDataList[ElementId]);
if (ActionTask.Execute(ElementData) == EActionResult::Retry)
{
ActionQueues[EQueueTask::NonAsyncQueue].Enqueue(MoveTemp(ActionTask));
continue;
}
}
}
if (TasksToComplete == EWorkerTask::NoTask && SceneElement.IsValid())
{
// Terminate all rendering commands before deleting any asset
FlushRenderingCommands();
// Delete assets which has not been reused on the last processing
if (FAssetRegistry::CleanUp())
{
if (!IsGarbageCollecting())
{
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
}
else
{
// Garbage collection has not been performed. Do it on next frame
TasksToComplete = EWorkerTask::GarbageCollect;
return;
}
}
TRACE_BOOKMARK(TEXT("Load complete - %s"), SceneElement->GetName());
OnGoingTasks.Empty();
LastSceneGuid = SceneElement->GetSharedState()->GetGuid();
// Free up the translator since it is not needed anymore
Translator.Reset();
Cast<ADatasmithRuntimeActor>(RootComponent->GetOwner())->OnImportEnd();
#ifdef LIVEUPDATE_TIME_LOGGING
double ElapsedSeconds = FPlatformTime::Seconds() - GlobalStartTime;
int ElapsedMin = int(ElapsedSeconds / 60.0);
ElapsedSeconds -= 60.0 * (double)ElapsedMin;
UE_LOG(LogDatasmithRuntime, Log, TEXT("Total load time is [%d min %.3f s]"), ElapsedMin, ElapsedSeconds);
#endif
}
}
void FSceneImporter::Reset(bool bIsNewScene)
{
bIncrementalUpdate = false;
// Hide all imported scene components if a new scene is going to be imported.
if (bIsNewScene)
{
for (TPair< FSceneGraphId, FActorData >& Pair : ActorDataList)
{
if (USceneComponent* SceneComponent = Pair.Value.GetObject<USceneComponent>())
{
HideSceneComponent(SceneComponent);
}
}
}
// Clear all cached data if it is a new scene
SceneElement.Reset();
LastSceneGuid = FGuid();
MetadataCollect = TFuture<void>();
TasksToComplete = EWorkerTask::ResetScene;
// Empty tasks queues
for (TQueue< FActionTask, EQueueMode::Mpsc >& Queue : ActionQueues)
{
Queue.Empty();
}
// Reset counters
QueuedTaskCount = 0;
// Empty tracking arrays and sets
MeshElementSet.Empty();
TextureElementSet.Empty();
MaterialElementSet.Empty();
// #ue_datasmithruntime: What about lightmap weights on incremental update?
LightmapWeights.Empty();
}
bool FSceneImporter::IncrementalUpdate(TSharedRef< IDatasmithScene > InSceneElement, FUpdateContext& UpdateContext)
{
#ifdef LIVEUPDATE_TIME_LOGGING
GlobalStartTime = FPlatformTime::Seconds();
#endif
UE_LOG(LogDatasmithRuntime, Log, TEXT("Incremental update..."));
SceneElement = InSceneElement;
ensure(SceneElement.IsValid());
Translator = MakeShared<FDatasmithNativeTranslator>();
PrepareIncrementalUpdate(UpdateContext);
IncrementalAdditions(UpdateContext.Additions, UpdateContext.Updates);
IncrementalModifications(UpdateContext.Updates);
IncrementalDeletions(UpdateContext.Deletions);
bIncrementalUpdate = true;
TasksToComplete |= EWorkerTask::CollectSceneData;
return true;
}
void FSceneImporter::IncrementalModifications(TArray<TSharedPtr<IDatasmithElement>>& Modifications)
{
if (Modifications.Num() == 0)
{
return;
}
for (TSharedPtr<IDatasmithElement>& ElementPtr : Modifications)
{
if (Elements.Contains(ElementPtr->GetNodeId()))
{
FSceneGraphId ElementId = ElementPtr->GetNodeId();
if (AssetDataList.Contains(ElementId))
{
const EDataType DataType = AssetDataList[ElementId].Type;
const FString& Prefix = DataType == EDataType::Texture ? TexturePrefix : (DataType == EDataType::Material ? MaterialPrefix : MeshPrefix);
UE_LOG(LogDatasmithRuntime, Log, TEXT("IncrementalModifications: %s %s (%d)"), *Prefix, ElementPtr->GetName(), ElementPtr->GetNodeId());
const FString PrefixedName = Prefix + ElementPtr->GetName();
if (!AssetElementMapping.Contains(PrefixedName))
{
for (TPair<FString, FSceneGraphId>& Entry : AssetElementMapping)
{
if (Entry.Value == ElementId)
{
const FString OldKey = Entry.Key;
AssetElementMapping.Remove(OldKey);
break;
}
}
AssetElementMapping.Add(PrefixedName, ElementId);
}
FActionTaskFunction TaskFunc;
if (ElementPtr->IsA(EDatasmithElementType::BaseMaterial))
{
TaskFunc = [this, ElementId](UObject*, const FReferencer&) -> EActionResult::Type
{
FAssetData& MaterialData = this->AssetDataList[ElementId];
MaterialData.SetState(EAssetState::Unknown);
this->ProcessMaterialData(MaterialData);
ActionCounter.Increment();
return EActionResult::Succeeded;
};
}
else if (ElementPtr->IsA(EDatasmithElementType::StaticMesh))
{
TaskFunc = [this, ElementId](UObject*, const FReferencer&) -> EActionResult::Type
{
FAssetData& MeshData = this->AssetDataList[ElementId];
MeshData.SetState(EAssetState::Unknown);
ActionCounter.Increment();
this->ProcessMeshData(MeshData);
return EActionResult::Succeeded;
};
}
else if (ElementPtr->IsA(EDatasmithElementType::Texture))
{
ensure(TextureDataList.Contains(ElementId));
TaskFunc = [this, ElementId](UObject*, const FReferencer&) -> EActionResult::Type
{
FAssetData& TextureData = this->AssetDataList[ElementId];
TextureData.SetState(EAssetState::Unknown);
this->ProcessTextureData(ElementId);
ActionCounter.Increment();
return EActionResult::Succeeded;
};
}
AddToQueue(EQueueTask::UpdateQueue, { MoveTemp(TaskFunc), FReferencer() } );
TasksToComplete |= EWorkerTask::SetupTasks;
}
else if (ActorDataList.Contains(ElementId))
{
FActorData& ActorData = ActorDataList[ElementId];
UE_LOG(LogDatasmithRuntime, Log, TEXT("IncrementalModifications: Actor %s (%d)"), ElementPtr->GetName(), ElementPtr->GetNodeId());
ActorData.SetState(EAssetState::Unknown);
}
}
else if (DependencyList.Contains(ElementPtr->GetNodeId()))
{
ProcessDependency(ElementPtr);
}
}
}
void FSceneImporter::IncrementalDeletions(TArray<DirectLink::FSceneGraphId>& Deletions)
{
if (Deletions.Num() == 0)
{
return;
}
FActionTaskFunction TaskFunc = [this](UObject*, const FReferencer& Referencer) -> EActionResult::Type
{
EActionResult::Type Result = this->DeleteElement(Referencer.GetId());
if(Result == EActionResult::Succeeded)
{
this->TasksToComplete |= EWorkerTask::GarbageCollect;
}
return Result;
};
bool bFlushRenderingCommands = false;
for (DirectLink::FSceneGraphId& ElementId : Deletions)
{
if (Elements.Contains(ElementId))
{
if (AssetDataList.Contains(ElementId))
{
if (!AssetDataList[ElementId].HasState(EAssetState::PendingDelete))
{
continue;
}
const EDataType DataType = AssetDataList[ElementId].Type;
const FString& Prefix = DataType == EDataType::Texture ? TexturePrefix : (DataType == EDataType::Material ? MaterialPrefix : MeshPrefix);
UE_LOG(LogDatasmithRuntime, Log, TEXT("IncrementalDeletions: %s %d"), *Prefix, ElementId);
AddToQueue(EQueueTask::DeleteAssetQueue, { TaskFunc, FReferencer(ElementId) } );
TasksToComplete |= EWorkerTask::DeleteAsset;
}
else if (ActorDataList.Contains(ElementId))
{
UE_LOG(LogDatasmithRuntime, Log, TEXT("IncrementalDeletions: actor %s (%d)"), Elements[ElementId]->GetLabel(), ElementId);
HideSceneComponent(ActorDataList[ElementId].GetObject<USceneComponent>());
AddToQueue(EQueueTask::DeleteCompQueue, { TaskFunc, FReferencer(ElementId) } );
TasksToComplete |= EWorkerTask::DeleteComponent;
bFlushRenderingCommands = true;
}
// Remove metadata from list of tracked elements
else if (Elements[ElementId]->IsA(EDatasmithElementType::MetaData))
{
AddToQueue(EQueueTask::DeleteCompQueue, { TaskFunc, FReferencer(ElementId) } );
TasksToComplete |= EWorkerTask::DeleteComponent;
}
else
{
UE_LOG(LogDatasmithRuntime, Error, TEXT("Element %d (%s) was not found"), ElementId, Elements[ElementId]->GetName());
ensure(false);
}
}
}
if (bFlushRenderingCommands)
{
FlushRenderingCommands();
}
}
void FSceneImporter::ProcessDependency(const TSharedPtr<IDatasmithElement>& Element)
{
FSceneGraphId ElementId = Element->GetNodeId();
FReferencer Referencer = DependencyList[ElementId];
if (Element->IsA(EDatasmithElementType::MaterialId))
{
const IDatasmithMaterialIDElement* MaterialIDElement = static_cast<const IDatasmithMaterialIDElement*>(Element.Get());
if (FSceneGraphId* MaterialElementIdPtr = AssetElementMapping.Find(MaterialPrefix + MaterialIDElement->GetName()))
{
FActionTaskFunction AssignMaterialFunc = [this](UObject* Object, const FReferencer& Referencer) -> EActionResult::Type
{
return this->AssignMaterial(Referencer, Cast<UMaterialInstanceDynamic>(Object));
};
AddToQueue(EQueueTask::NonAsyncQueue, { AssignMaterialFunc, *MaterialElementIdPtr, MoveTemp(Referencer) });
TasksToComplete |= EWorkerTask::MaterialAssign;
}
// The value of a property has changed, process it
else if (Element->IsA(EDatasmithElementType::KeyValueProperty))
{
// If it is a material's property, invalidate material and queue its processing
if (Referencer.GetType() == EDataType::Material)
{
if (AssetDataList.Contains(Referencer.ElementId))
{
FActionTaskFunction TaskFunc = [this, ElementId = Referencer.ElementId](UObject*, const FReferencer&) -> EActionResult::Type
{
FAssetData& MaterialData = this->AssetDataList[ElementId];
MaterialData.SetState(EAssetState::Unknown);
this->ProcessMaterialData(MaterialData);
ActionCounter.Increment();
return EActionResult::Succeeded;
};
AddToQueue(EQueueTask::UpdateQueue, { MoveTemp(TaskFunc), FReferencer() } );
}
}
// If it is a metadata's property, queue its application to the associated element
else if (Referencer.GetType() == EDataType::Metadata)
{
//
if (Elements.Contains(Referencer.ElementId))
{
FActionTaskFunction TaskFunc = [this, ElementId = Referencer.ElementId](UObject*, const FReferencer&) -> EActionResult::Type
{
TSharedPtr<IDatasmithMetaDataElement> MetadataElement = StaticCastSharedPtr<IDatasmithMetaDataElement>(Elements[ElementId]);
const TSharedPtr< IDatasmithElement >& AssociatedElement = MetadataElement->GetAssociatedElement();
if (AssociatedElement && Elements.Contains(AssociatedElement->GetNodeId()))
{
const FSceneGraphId AssociatedId = AssociatedElement->GetNodeId();
if (AssetDataList.Contains(AssociatedId))
{
ApplyMetadata(ElementId, AssetDataList[AssociatedId].GetObject());
}
else if (ActorDataList.Contains(AssociatedId))
{
ApplyMetadata(ElementId, ActorDataList[AssociatedId].GetObject());
}
}
ActionCounter.Increment();
return EActionResult::Succeeded;
};
AddToQueue(EQueueTask::UpdateQueue, { MoveTemp(TaskFunc), FReferencer() } );
}
}
}
}
}
void FSceneImporter::IncrementalAdditions(TArray<TSharedPtr<IDatasmithElement>>& Additions, TArray<TSharedPtr<IDatasmithElement>>& Updates)
{
const int32 AdditionCount = Additions.Num();
TextureElementSet.Empty(AdditionCount);
MeshElementSet.Empty(AdditionCount);
MaterialElementSet.Empty(AdditionCount);
if (AdditionCount == 0)
{
return;
}
// Collect set of new textures, materials and meshes used in scene
Elements.Reserve( Elements.Num() + AdditionCount );
AssetDataList.Reserve( AssetDataList.Num() + AdditionCount );
TFunction<void(TSharedPtr<IDatasmithElement>&&, EDataType)> LocalAddAsset;
LocalAddAsset = [&](TSharedPtr<IDatasmithElement>&& Element, EDataType DataType) -> void
{
const FString& Prefix = DataType == EDataType::Texture ? TexturePrefix : (DataType == EDataType::Material ? MaterialPrefix : MeshPrefix);
const FString PrefixedName = Prefix + Element->GetName();
const FSceneGraphId ElementId = Element->GetNodeId();
// If the new asset has the same name as an existing one, mark it as not processed
if (this->AssetElementMapping.Contains(PrefixedName))
{
const FSceneGraphId ExistingElementId = AssetElementMapping[PrefixedName];
FAssetData& ExistingAssetData = AssetDataList[ExistingElementId];
if (!ExistingAssetData.HasState(EAssetState::PendingDelete))
{
UE_LOG(LogDatasmithRuntime, Error, TEXT("Found a new %s (%d) with the same name, %s, as an existing one (%d)."), *Prefix, ElementId, this->Elements[ExistingElementId]->GetName(), ExistingElementId);
}
// Add all referencers to the list of elements to update
for (const FReferencer& Referencer : ExistingAssetData.Referencers)
{
if (this->AssetDataList.Contains(Referencer.ElementId))
{
FAssetData& ReferencerAssetData = this->AssetDataList[Referencer.ElementId];
const bool bMustBeProcessed = ReferencerAssetData.HasState(EAssetState::Processed | EAssetState::Completed) && !ReferencerAssetData.HasState(EAssetState::PendingDelete);
if (bMustBeProcessed)
{
ReferencerAssetData.ClearState(EAssetState::Processed);
Updates.Add(Elements[Referencer.ElementId]);
}
}
else if (this->ActorDataList.Contains(Referencer.ElementId))
{
this->ActorDataList[Referencer.ElementId].ClearState(EAssetState::Processed);
}
}
this->AssetElementMapping[PrefixedName] = ElementId;
}
else
{
this->AssetElementMapping.Add(PrefixedName, ElementId);
}
this->Elements.Add(ElementId, MoveTemp(Element));
FAssetData AssetData(ElementId, DataType);
this->AssetDataList.Emplace(ElementId, MoveTemp(AssetData));
};
for (TSharedPtr<IDatasmithElement>& ElementPtr : Additions)
{
if (ElementPtr->IsA(EDatasmithElementType::BaseMaterial))
{
UE_LOG(LogDatasmithRuntime, Log, TEXT("IncrementalAdditions: Material %s (%d)"), ElementPtr->GetName(), ElementPtr->GetNodeId());
LocalAddAsset(MoveTemp(ElementPtr), EDataType::Material);
}
else if (ElementPtr->IsA(EDatasmithElementType::StaticMesh))
{
UE_LOG(LogDatasmithRuntime, Log, TEXT("IncrementalAdditions: StaticMesh %s (%d)"), ElementPtr->GetName(), ElementPtr->GetNodeId());
if (IDatasmithMeshElement* MeshElement = static_cast<IDatasmithMeshElement*>(ElementPtr.Get()))
{
// If resource file does not exist, add scene's resource path if valid
if (!FPaths::FileExists(MeshElement->GetFile()) && FPaths::DirectoryExists(SceneElement->GetResourcePath()))
{
MeshElement->SetFile( *FPaths::Combine(SceneElement->GetResourcePath(), MeshElement->GetFile()) );
}
// Only add the mesh if its associated mesh file exists
if (FPaths::FileExists(MeshElement->GetFile()))
{
LocalAddAsset(MoveTemp(ElementPtr), EDataType::Mesh);
}
}
}
else if (ElementPtr->IsA(EDatasmithElementType::Texture))
{
UE_LOG(LogDatasmithRuntime, Log, TEXT("IncrementalAdditions: Texture %s (%d)"), ElementPtr->GetName(), ElementPtr->GetNodeId());
if (IDatasmithTextureElement* TextureElement = static_cast<IDatasmithTextureElement*>(ElementPtr.Get()))
{
// If resource file does not exist, add scene's resource path if valid
if (!FPaths::FileExists(TextureElement->GetFile()) && FPaths::DirectoryExists(SceneElement->GetResourcePath()))
{
TextureElement->SetFile( *FPaths::Combine(SceneElement->GetResourcePath(), TextureElement->GetFile()) );
}
if (FPaths::FileExists(TextureElement->GetFile()))
{
LocalAddAsset(MoveTemp(ElementPtr), EDataType::Texture);
}
}
}
else if (ImportOptions.bImportMetaData && ElementPtr->IsA(EDatasmithElementType::MetaData))
{
ProcessMetdata(StaticCastSharedPtr<IDatasmithMetaDataElement>(ElementPtr));
}
else if (ElementPtr->IsA(EDatasmithElementType::Actor))
{
UE_LOG(LogDatasmithRuntime, Log, TEXT("IncrementalAdditions: Actor %s (%d)"), ElementPtr->GetName(), ElementPtr->GetNodeId());
}
}
TasksToComplete |= EWorkerTask::SetupTasks;
}
void FSceneImporter::PrepareIncrementalUpdate(FUpdateContext& UpdateContext)
{
TasksToComplete = EWorkerTask::NoTask;
// Update elements map with new pointers
for (int32 Index = 0; Index < SceneElement->GetTexturesCount(); ++Index)
{
FSceneGraphId ElementId = SceneElement->GetTexture(Index)->GetNodeId();
if (this->Elements.Contains(ElementId))
{
Elements[ElementId] = SceneElement->GetTexture(Index);
}
}
for (int32 Index = 0; Index < SceneElement->GetMaterialsCount(); ++Index)
{
FSceneGraphId ElementId = SceneElement->GetMaterial(Index)->GetNodeId();
if (this->Elements.Contains(ElementId))
{
this->Elements[ElementId] = SceneElement->GetMaterial(Index);
}
}
for (int32 Index = 0; Index < SceneElement->GetMeshesCount(); ++Index)
{
const FSceneGraphId ElementId = SceneElement->GetMesh(Index)->GetNodeId();
if (this->Elements.Contains(ElementId))
{
this->Elements[ElementId] = SceneElement->GetMesh(Index);
}
}
for (int32 Index = 0; Index < SceneElement->GetActorsCount(); ++Index)
{
ParseScene(SceneElement->GetActor(Index), DirectLink::InvalidId,
[this](const TSharedPtr<IDatasmithActorElement>& ActorElement, FSceneGraphId ParentId) -> void
{
FSceneGraphId ElementId = ActorElement->GetNodeId();
if (this->Elements.Contains(ElementId))
{
this->Elements[ElementId] = ActorElement;
}
}
);
}
for (int32 Index = 0; Index < SceneElement->GetMetaDataCount(); ++Index)
{
const FSceneGraphId ElementId = SceneElement->GetMetaData(Index)->GetNodeId();
if (this->Elements.Contains(ElementId))
{
this->Elements[ElementId] = SceneElement->GetMetaData(Index);
}
}
// Clear 'Processed' state of modified elements
for (TSharedPtr<IDatasmithElement>& ElementPtr : UpdateContext.Updates)
{
const FSceneGraphId ElementId = ElementPtr->GetNodeId();
if (AssetDataList.Contains(ElementId))
{
AssetDataList[ElementId].ClearState(EAssetState::Processed);
}
else if (ActorDataList.Contains(ElementId))
{
ActorDataList[ElementId].ClearState(EAssetState::Processed);
}
}
// Mark assets which are about to be deleted with 'PendingDelete'
for (DirectLink::FSceneGraphId& ElementId : UpdateContext.Deletions)
{
if (FAssetData* AssetData = AssetDataList.Find(ElementId))
{
AssetData->AddState(EAssetState::PendingDelete);
}
else if (FActorData* ActorData = ActorDataList.Find(ElementId))
{
ActorData->AddState(EAssetState::PendingDelete);
}
}
// Verify that deleted assets are not referenced anymore
for (DirectLink::FSceneGraphId& ElementId : UpdateContext.Deletions)
{
if (AssetDataList.Contains(ElementId))
{
for (FReferencer& Referencer : AssetDataList[ElementId].Referencers)
{
if (AssetDataList.Contains(Referencer.ElementId))
{
FAssetData& AssetData = AssetDataList[Referencer.ElementId];
if (AssetData.HasState(EAssetState::Processed) && !AssetData.HasState(EAssetState::PendingDelete))
{
const TCHAR* ElementName = Elements[ElementId]->GetName();
const TCHAR* ReferencerName = Elements[Referencer.ElementId]->GetName();
UE_LOG(LogDatasmithRuntime, Error, TEXT("Element %s (%d) marked for deletion but referencer %s (%d) is neither marked for deletion or for update"), ElementName, ElementId, ReferencerName, Referencer.ElementId);
AssetData.ClearState(EAssetState::Processed);
UpdateContext.Updates.Add(Elements[Referencer.ElementId]);
}
}
}
}
}
// Parse scene to mark all existing actors as not processed
for (int32 Index = 0; Index < SceneElement->GetActorsCount(); ++Index)
{
ParseScene(SceneElement->GetActor(Index), DirectLink::InvalidId,
[this](const TSharedPtr<IDatasmithActorElement>& ActorElement, FSceneGraphId ParentId) -> void
{
FSceneGraphId ElementId = ActorElement->GetNodeId();
if (ActorDataList.Contains(ElementId))
{
ActorDataList[ElementId].ClearState(EAssetState::Processed);
}
}
);
}
for (int32 Index = 0; Index < EQueueTask::MaxQueues; ++Index)
{
ActionQueues[Index].Empty();
}
}
EActionResult::Type FSceneImporter::DeleteElement(FSceneGraphId ElementId)
{
bool bDeletionSuccessful = false;
bool bHasSomethingToDelete = false;
FAssetData AssetData(DirectLink::InvalidId);
if (AssetDataList.RemoveAndCopyValue(ElementId, AssetData))
{
bHasSomethingToDelete = AssetData.HasState(EAssetState::Completed) && !AssetData.HasState(EAssetState::Skipped);
bDeletionSuccessful = DeleteAsset(AssetData);
}
FActorData ActorData(DirectLink::InvalidId);
if (ActorDataList.RemoveAndCopyValue(ElementId, ActorData))
{
bHasSomethingToDelete = ActorData.HasState(EAssetState::Completed) && !ActorData.HasState(EAssetState::Skipped);
bDeletionSuccessful = DeleteComponent(ActorData);
}
ensure(bDeletionSuccessful || !bHasSomethingToDelete);
TSharedPtr<IDatasmithElement> ElementPtr;
return bDeletionSuccessful && Elements.RemoveAndCopyValue(ElementId, ElementPtr) ? EActionResult::Succeeded : EActionResult::Failed;
}
bool FSceneImporter::DeleteAsset(FAssetData& AssetData)
{
FString AssetPrefixedName;
const FSceneGraphId ElementId = AssetData.ElementId;
const EDataType DataType(AssetData.Type);
if (DataType == EDataType::Texture)
{
AssetPrefixedName = TexturePrefix + Elements[ElementId]->GetName();
}
else if (DataType == EDataType::Material || DataType == EDataType::PbrMaterial)
{
// If asset is a material and it references textures, remove it from the textures' list of referencers
FTextureCallback TextureCallback;
TextureCallback = [this, ElementId](const FString& TextureNamePrefixed, int32 PropertyIndex)->void
{
this->RemoveFromReferencer(this->AssetElementMapping.Find(TextureNamePrefixed),ElementId);
};
TSharedPtr< IDatasmithElement >& Element = Elements[ AssetData.ElementId ];
if( Element->IsA( EDatasmithElementType::UEPbrMaterial ) )
{
ProcessMaterialElement(static_cast<IDatasmithUEPbrMaterialElement*>(Element.Get()), TextureCallback);
}
else if( Element->IsA( EDatasmithElementType::MaterialInstance ) )
{
ProcessMaterialElement(StaticCastSharedPtr<IDatasmithMaterialInstanceElement>(Element), TextureCallback);
}
AssetPrefixedName = MaterialPrefix + Element->GetName();
}
else if (DataType == EDataType::Mesh)
{
// If asset is a mesh and it references materials, remove it from the materials' list of referencers
TSharedPtr< IDatasmithMeshElement > MeshElement = StaticCastSharedPtr< IDatasmithMeshElement >(Elements[ElementId]);
for (int32 Index = 0; Index < MeshElement->GetMaterialSlotCount(); Index++)
{
if (const IDatasmithMaterialIDElement* MaterialIDElement = MeshElement->GetMaterialSlotAt(Index).Get())
{
const FString MaterialPathName(MaterialIDElement->GetName());
if (!MaterialPathName.StartsWith(TEXT("/")))
{
RemoveFromReferencer(AssetElementMapping.Find(MaterialPrefix + MaterialPathName), ElementId);
}
}
}
AssetPrefixedName = MeshPrefix + MeshElement->GetName();
}
ensure(AssetElementMapping.Contains(AssetPrefixedName));
// ElementId may mismatch if new object of same name but new id was added
if (AssetElementMapping[AssetPrefixedName] == AssetData.ElementId)
{
AssetElementMapping.Remove(AssetPrefixedName);
}
if (UObject* Asset = AssetData.Object.Get())
{
AssetData.Object.Reset();
FAssetRegistry::UnregisterAssetData(Asset, SceneKey, AssetData.ElementId);
}
return true;
}
void FSceneImporter::ProcessMetdata(const TSharedPtr<IDatasmithMetaDataElement>& MetadataElement)
{
// Process metadata only if it has properties and the associated element is tracked
if (MetadataElement && MetadataElement->GetPropertiesCount() > 0)
{
const TSharedPtr< IDatasmithElement >& AssociatedElement = MetadataElement->GetAssociatedElement();
const FSceneGraphId AssociatedId = AssociatedElement ? AssociatedElement->GetNodeId() : DirectLink::InvalidId;
if (Elements.Contains(AssociatedId))
{
if (AssetDataList.Contains(AssociatedId))
{
AssetDataList[AssociatedId].MetadataId = MetadataElement->GetNodeId();
}
else if (ActorDataList.Contains(AssociatedId))
{
FActorData& ActorData = ActorDataList[AssociatedId];
ActorData.MetadataId = MetadataElement->GetNodeId();
// Record task to assign metadata if the actor has already been created.
// This happens for 'simple' actor, i.e. container of child actors.
if (ActorData.HasState(EAssetState::Completed))
{
FActionTaskFunction ApplyMetadataFunc = [this](UObject* Object, const FReferencer& Referencer) -> EActionResult::Type
{
if (USceneComponent* SceneComponent = Cast<USceneComponent>(Object))
{
this->ApplyMetadata(Referencer.GetId(), SceneComponent);
return EActionResult::Succeeded;
}
return EActionResult::Failed;
};
AddToQueue(EQueueTask::NonAsyncQueue, { ApplyMetadataFunc, ActorData.ElementId, { EDataType::Metadata, ActorData.MetadataId, 0 } });
TasksToComplete |= EWorkerTask::ComponentFinalize;
}
}
Elements.Add(MetadataElement->GetNodeId(), MetadataElement);
}
else if (AssociatedElement == SceneElement)
{
Elements.Add(MetadataElement->GetNodeId(), MetadataElement);
ApplyMetadata(MetadataElement->GetNodeId(), RootComponent.Get());
}
}
}
// Logic borrowed from FDatasmithImporter::ImportMetaDataForObject
void FSceneImporter::ApplyMetadata(FSceneGraphId MetadataId, UObject* Object)
{
if ( !Object || !Object->GetClass()->ImplementsInterface( UInterface_AssetUserData::StaticClass() ) || MetadataId == DirectLink::InvalidId)
{
return;
}
if (IDatasmithMetaDataElement* MetadataElement = static_cast<IDatasmithMetaDataElement*>(Elements[MetadataId].Get()))
{
if (IInterface_AssetUserData* AssetUserData = Cast< IInterface_AssetUserData >(Object))
{
UDatasmithAssetUserData* DatasmithUserData = AssetUserData->GetAssetUserData< UDatasmithAssetUserData >();
if ( !DatasmithUserData )
{
DatasmithUserData = NewObject<UDatasmithAssetUserData>( Object, NAME_None, RF_Public | RF_Transactional );
AssetUserData->AddAssetUserData( DatasmithUserData );
}
UDatasmithAssetUserData::FMetaDataContainer MetaData;
const int32 PropertiesCount = MetadataElement->GetPropertiesCount();
MetaData.Reserve( PropertiesCount + 1 );
// Add associated element's unique id
MetaData.Add( UDatasmithAssetUserData::UniqueIdMetaDataKey, MetadataElement->GetAssociatedElement()->GetName() );
// Add Datasmith metadata's properties
for ( int32 PropertyIndex = 0; PropertyIndex < PropertiesCount; ++PropertyIndex )
{
const TSharedPtr<IDatasmithKeyValueProperty>& Property = MetadataElement->GetProperty( PropertyIndex );
MetaData.Add( Property->GetName(), Property->GetValue() );
DependencyList.Add(Property->GetNodeId(), { EDataType::Metadata, MetadataId, 0xffff });
}
MetaData.KeySort(FNameLexicalLess());
DatasmithUserData->MetaData = MoveTemp( MetaData );
}
}
}
void FSceneImporter::RemoveFromReferencer(FSceneGraphId* AssetIdPtr, FSceneGraphId ReferencerId)
{
if (AssetIdPtr)
{
TArray<FReferencer>& Referencers = AssetDataList[*AssetIdPtr].Referencers;
for (int32 Index = 0; Index < Referencers.Num(); ++Index)
{
if (Referencers[Index].ElementId == ReferencerId)
{
Referencers.RemoveAt(Index, EAllowShrinking::No);
return;
}
}
}
}
} // End of namespace DatasmithRuntime