void SDetailsViewBase::RestoreExpandedItems(TSharedRef<FPropertyNode> InitialStartNode) { TSharedPtr<FPropertyNode> StartNode = InitialStartNode; ExpandedDetailNodes.Empty(); FString ExpandedCustomItems; UStruct* BestBaseStruct = StartNode->FindComplexParent()->GetBaseStructure(); //while a valid class, and we're either the same as the base class (for multiple actors being selected and base class is AActor) OR we're not down to AActor yet) TArray<FString> DetailPropertyExpansionStrings; for (UStruct* Struct = BestBaseStruct; Struct && ((BestBaseStruct == Struct) || (Struct != AActor::StaticClass())); Struct = Struct->GetSuperStruct()) { GConfig->GetSingleLineArray(TEXT("DetailPropertyExpansion"), *Struct->GetName(), DetailPropertyExpansionStrings, GEditorPerProjectIni); } TSet<FString> ExpandedPropertyItems; ExpandedPropertyItems.Append(DetailPropertyExpansionStrings); SetExpandedItems(StartNode, ExpandedPropertyItems); if (BestBaseStruct) { GConfig->GetString(TEXT("DetailCustomWidgetExpansion"), *BestBaseStruct->GetName(), ExpandedCustomItems, GEditorPerProjectIni); TArray<FString> ExpandedCustomItemsArray; ExpandedCustomItems.ParseIntoArray(ExpandedCustomItemsArray, TEXT(","), true); ExpandedDetailNodes.Append(ExpandedCustomItemsArray); } }
void FGatherConvertedClassDependencies::GatherAssetReferencedByUDSDefaultValue(TSet<UObject*>& Dependencies, UUserDefinedStruct* Struct) { if (Struct) { FStructOnScope StructOnScope(Struct); Struct->InitializeDefaultValue(StructOnScope.GetStructMemory()); FArchiveReferencesInStructIntance ArchiveReferencesInStructIntance; Struct->SerializeItem(ArchiveReferencesInStructIntance, StructOnScope.GetStructMemory(), nullptr); Dependencies.Append(ArchiveReferencesInStructIntance.References); } }
// Imports a set of previously exported nodes into a graph void FEdGraphUtilities::ImportNodesFromText(UEdGraph* DestinationGraph, const FString& TextToImport, /*out*/ TSet<UEdGraphNode*>& ImportedNodeSet) { // Turn the text buffer into objects FGraphObjectTextFactory Factory(DestinationGraph); Factory.ProcessBuffer(DestinationGraph, RF_Transactional, TextToImport); // Fix up pin cross-links, etc... FEdGraphUtilities::PostProcessPastedNodes(Factory.SpawnedNodes); ImportedNodeSet.Append(Factory.SpawnedNodes); }
bool FVertexSnappingImpl::SnapLocationToNearestVertex( FVector& Location, const FVector2D& MouseLocation, FLevelEditorViewportClient* ViewportClient, FVector& OutVertexNormal, bool bDrawVertexHelpers ) { bool bSnapped = false; // Make a box around the actor which is the area we are allowed to snap in FBox AllowedSnappingBox = FBox( Location-VertexSnappingConstants::MaxSnappingDistance, Location+VertexSnappingConstants::MaxSnappingDistance ); FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues( ViewportClient->Viewport, ViewportClient->GetScene(), ViewportClient->EngineShowFlags ) .SetRealtimeUpdate( ViewportClient->IsRealtime() )); FSceneView* View = ViewportClient->CalcSceneView( &ViewFamily ); TArray<FSnapActor> ActorsInBox; TSet<TWeakObjectPtr<AActor> > ActorsToIgnore; // Ignore actors currently being moved ActorsToIgnore.Append( ViewportClient->GetDropPreviewActors() ); GetPossibleSnapActors( AllowedSnappingBox, MouseLocation.IntPoint(), ViewportClient, View, EAxisList::Screen, ActorsToIgnore, ActorsInBox ); FViewportCursorLocation Cursor(View, ViewportClient, MouseLocation.X, MouseLocation.Y ); FPlane ActorPlane( Location, Cursor.GetDirection() ); FVertexSnappingArgs Args ( ActorPlane, Location, ViewportClient, View, Cursor.GetCursorPos(), EAxisList::Screen, bDrawVertexHelpers ); // Snap to the nearest vertex FSnappingVertex ClosestVertex = GetClosestVertex( ActorsInBox, Args ); Location = ClosestVertex.Position; OutVertexNormal = ClosestVertex.Normal; bSnapped = true; return bSnapped; }
bool SDetailsView::ShouldSetNewObjects( const TArray< TWeakObjectPtr< UObject > >& InObjects ) const { bool bShouldSetObjects = false; const bool bHadBSPBrushSelected = SelectedActorInfo.bHaveBSPBrush; if( bHadBSPBrushSelected == true ) { // If a BSP brush was selected we need to refresh because surface could have been selected and the object set not updated bShouldSetObjects = true; } else if( InObjects.Num() != RootPropertyNode->GetNumObjects() ) { // If the object arrys differ in size then at least one object is different so we must reset bShouldSetObjects = true; } else { // Check to see if the objects passed in are different. If not we do not need to set anything TSet< TWeakObjectPtr< UObject > > NewObjects; NewObjects.Append( InObjects ); for ( TPropObjectIterator Itor( RootPropertyNode->ObjectIterator() ); Itor; ++Itor ) { TWeakObjectPtr<UObject> Object = *Itor; if( Object.IsValid() && !NewObjects.Contains( Object ) ) { // An existing object is not in the list of new objects to set bShouldSetObjects = true; break; } else if( !Object.IsValid() ) { // An existing object is invalid bShouldSetObjects = true; break; } } } return bShouldSetObjects; }
bool FPluginManager::ConfigureEnabledPlugins() { if(!bHaveConfiguredEnabledPlugins) { // Don't need to run this again bHaveConfiguredEnabledPlugins = true; // If a current project is set, check that we know about any plugin that's explicitly enabled const FProjectDescriptor *Project = IProjectManager::Get().GetCurrentProject(); const bool bHasProjectFile = Project != nullptr; // Get all the enabled plugin names TArray< FString > EnabledPluginNames; #if IS_PROGRAM // Programs can also define the list of enabled plugins in ini GConfig->GetArray(TEXT("Plugins"), TEXT("ProgramEnabledPlugins"), EnabledPluginNames, GEngineIni); #endif #if !IS_PROGRAM || HACK_HEADER_GENERATOR if (!FParse::Param(FCommandLine::Get(), TEXT("NoEnginePlugins"))) { FProjectManager::Get().GetEnabledPlugins(EnabledPluginNames); } #endif // Build a set from the array TSet< FString > AllEnabledPlugins; AllEnabledPlugins.Append(MoveTemp(EnabledPluginNames)); // Enable all the plugins by name for (const TSharedRef< FPlugin > Plugin : AllPlugins) { if (AllEnabledPlugins.Contains(Plugin->Name)) { Plugin->bEnabled = (!IS_PROGRAM || !bHasProjectFile) || IsPluginSupportedByCurrentTarget(Plugin); if (!Plugin->bEnabled) { AllEnabledPlugins.Remove(Plugin->Name); } } } if (bHasProjectFile) { // Take a copy of the Project's plugins as we may remove some TArray<FPluginReferenceDescriptor> PluginsCopy = Project->Plugins; for(const FPluginReferenceDescriptor& Plugin: PluginsCopy) { if ((Plugin.bEnabled && !FindPluginInstance(Plugin.Name).IsValid()) && (!IS_PROGRAM || AllEnabledPlugins.Contains(Plugin.Name))) // skip if this is a program and the plugin is not enabled { FText Caption(LOCTEXT("PluginMissingCaption", "Plugin missing")); if(Plugin.MarketplaceURL.Len() > 0) { if(FMessageDialog::Open(EAppMsgType::YesNo, FText::Format(LOCTEXT("PluginMissingError", "This project requires the {0} plugin.\n\nWould you like to download it from the the Marketplace?"), FText::FromString(Plugin.Name)), &Caption) == EAppReturnType::Yes) { FString Error; FPlatformProcess::LaunchURL(*Plugin.MarketplaceURL, nullptr, &Error); if(Error.Len() > 0) FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Error)); return false; } } else { FString Description = (Plugin.Description.Len() > 0) ? FString::Printf(TEXT("\n\n%s"), *Plugin.Description) : FString(); FMessageDialog::Open(EAppMsgType::Ok, FText::Format(LOCTEXT("PluginRequiredError", "This project requires the {0} plugin. {1}"), FText::FromString(Plugin.Name), FText::FromString(Description)), &Caption); if (FMessageDialog::Open(EAppMsgType::YesNo, FText::Format(LOCTEXT("PluginMissingDisable", "Would you like to disable {0}? You will no longer be able to open any assets created using it."), FText::FromString(Plugin.Name)), &Caption) == EAppReturnType::No) { return false; } FText FailReason; if (!IProjectManager::Get().SetPluginEnabled(*Plugin.Name, false, FailReason)) { FMessageDialog::Open(EAppMsgType::Ok, FailReason); } } } } } // If we made it here, we have all the required plugins bHaveAllRequiredPlugins = true; for(const TSharedRef<FPlugin>& Plugin: AllPlugins) { if (Plugin->bEnabled) { // Add the plugin binaries directory const FString PluginBinariesPath = FPaths::Combine(*FPaths::GetPath(Plugin->FileName), TEXT("Binaries"), FPlatformProcess::GetBinariesSubdirectory()); FModuleManager::Get().AddBinariesDirectory(*PluginBinariesPath, Plugin->LoadedFrom == EPluginLoadedFrom::GameProject); #if !IS_MONOLITHIC // Only check this when in a non-monolithic build where modules could be in separate binaries if (Project != NULL && Project->Modules.Num() == 0) { // Content only project - check whether any plugins are incompatible and offer to disable instead of trying to build them later TArray<FString> IncompatibleFiles; if (!FModuleDescriptor::CheckModuleCompatibility(Plugin->Descriptor.Modules, Plugin->LoadedFrom == EPluginLoadedFrom::GameProject, IncompatibleFiles)) { // Ask whether to disable plugin if incompatible FText Caption(LOCTEXT("IncompatiblePluginCaption", "Plugin missing or incompatible")); if (FMessageDialog::Open(EAppMsgType::YesNo, FText::Format(LOCTEXT("IncompatiblePluginText", "Missing or incompatible modules in {0} plugin - would you like to disable it? You will no longer be able to open any assets created using it."), FText::FromString(Plugin->Name)), &Caption) == EAppReturnType::No) { return false; } FText FailReason; if (!IProjectManager::Get().SetPluginEnabled(*Plugin->Name, false, FailReason)) { FMessageDialog::Open(EAppMsgType::Ok, FailReason); } } } #endif //!IS_MONOLITHIC // Build the list of content folders if (Plugin->Descriptor.bCanContainContent) { if (auto EngineConfigFile = GConfig->Find(GEngineIni, false)) { if (auto CoreSystemSection = EngineConfigFile->Find(TEXT("Core.System"))) { CoreSystemSection->AddUnique("Paths", Plugin->GetContentDir()); } } } // Load Default<PluginName>.ini config file if it exists FString PluginConfigDir = FPaths::GetPath(Plugin->FileName) / TEXT("Config/"); FConfigFile PluginConfig; FConfigCacheIni::LoadExternalIniFile(PluginConfig, *Plugin->Name, *FPaths::EngineConfigDir(), *PluginConfigDir, true); if (PluginConfig.Num() > 0) { FString PlaformName = FPlatformProperties::PlatformName(); FString PluginConfigFilename = FString::Printf(TEXT("%s%s/%s.ini"), *FPaths::GeneratedConfigDir(), *PlaformName, *Plugin->Name); FConfigFile& NewConfigFile = GConfig->Add(PluginConfigFilename, FConfigFile()); NewConfigFile.AddMissingProperties(PluginConfig); NewConfigFile.Write(PluginConfigFilename); } } } // Mount all the plugin content folders and pak files TArray<FString> FoundPaks; FPakFileSearchVisitor PakVisitor(FoundPaks); IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); for(TSharedRef<IPlugin> Plugin: GetEnabledPlugins()) { if (Plugin->CanContainContent() && ensure(RegisterMountPointDelegate.IsBound())) { FString ContentDir = Plugin->GetContentDir(); RegisterMountPointDelegate.Execute(Plugin->GetMountedAssetPath(), ContentDir); // Pak files are loaded from <PluginName>/Content/Paks/<PlatformName> if (FPlatformProperties::RequiresCookedData()) { FoundPaks.Reset(); PlatformFile.IterateDirectoryRecursively(*(ContentDir / TEXT("Paks") / FPlatformProperties::PlatformName()), PakVisitor); for (const auto& PakPath : FoundPaks) { if (FCoreDelegates::OnMountPak.IsBound()) { FCoreDelegates::OnMountPak.Execute(PakPath, 0); } } } } } } return bHaveAllRequiredPlugins; }
bool SDetailsView::ShouldSetNewObjects(const TArray< TWeakObjectPtr< UObject > >& InObjects) const { bool bShouldSetObjects = false; const bool bHadBSPBrushSelected = SelectedActorInfo.bHaveBSPBrush; if( bHadBSPBrushSelected == true ) { // If a BSP brush was selected we need to refresh because surface could have been selected and the object set not updated bShouldSetObjects = true; } else if( InObjects.Num() != GetNumObjects() ) { // If the object arrays differ in size then at least one object is different so we must reset bShouldSetObjects = true; } else if(InObjects.Num() == 0) { // User is likely resetting details panel bShouldSetObjects = true; } else { // Check to see if the objects passed in are different. If not we do not need to set anything TSet< TWeakObjectPtr< UObject > > NewObjects; NewObjects.Append(InObjects); if(DetailsViewArgs.bAllowMultipleTopLevelObjects) { // For multiple top level node support, if the single object in each node is not found in the new object set // then we need to refresh for(int32 RootNodeIndex = 0; RootNodeIndex < RootPropertyNodes.Num(); ++RootNodeIndex) { FObjectPropertyNode* RootPropertyNode = RootPropertyNodes[RootNodeIndex]->AsObjectNode(); if(RootPropertyNode && RootPropertyNode->GetNumObjects() > 0) { if(!NewObjects.Contains(RootPropertyNode->GetUObject(0))) { bShouldSetObjects = true; break; } } else { bShouldSetObjects = true; break; } } } else { ensure(RootPropertyNodes.Num() == 1); FObjectPropertyNode* RootPropertyNode = RootPropertyNodes[0]->AsObjectNode(); if( RootPropertyNode ) { for(TPropObjectIterator Itor(RootPropertyNode->ObjectIterator()); Itor; ++Itor) { TWeakObjectPtr<UObject> Object = *Itor; if(Object.IsValid() && !NewObjects.Contains(Object)) { // An existing object is not in the list of new objects to set bShouldSetObjects = true; break; } else if(!Object.IsValid()) { // An existing object is invalid bShouldSetObjects = true; break; } } } else { bShouldSetObjects = true; } } } if (!bShouldSetObjects && AssetSelectionUtils::IsAnySurfaceSelected(nullptr)) { bShouldSetObjects = true; } return bShouldSetObjects; }
bool FBuildDataCompactifier::Compactify(const TArray<FString>& ManifestsToKeep, const float DataAgeThreshold) const { GLog->Logf(TEXT("Running Compactify on %s%s"), *CloudDir, bPreview ? TEXT(". Preview mode. NO action will be taken.") : bNoPatchDelete ? TEXT(". NoPatchDelete mode. NO patch data will be deleted.") : TEXT("")); if (ManifestsToKeep.Num() > 0) { GLog->Logf(TEXT("Preserving manifest files: %s"), *FString::Join(ManifestsToKeep, TEXT(", "))); } GLog->Logf(TEXT("Minimum age of deleted chunks: %.3f days"), DataAgeThreshold); // We'll work out the date of the oldest unreferenced file we'll keep FDateTime Cutoff = FDateTime::UtcNow() - FTimespan::FromDays(DataAgeThreshold); // We'll get ALL files first, so we can use the count to preallocate space within the data filenames array to save excessive reallocs TArray<FString> AllFiles; const bool bFindFiles = true; const bool bFindDirectories = false; IFileManager::Get().FindFilesRecursive(AllFiles, *CloudDir, TEXT("*.*"), bFindFiles, bFindDirectories); TSet<FGuid> ReferencedGuids; // The master list of *ALL* referenced chunk / file data Guids TArray<FString> ManifestFilenames; TArray<FString> DeletedManifestFilenames; TArray<FGuid> DataGuids; // The Guids associated with the data files from a single manifest int32 NumDataFiles = 0; uint64 ManifestBytesDeleted = 0; // Preallocate enough storage in DataGuids, to stop repeatedly expanding the allocation DataGuids.Reserve(AllFiles.Num()); EnumerateManifests(ManifestFilenames); if (!DoAllManifestsExist(ManifestFilenames, ManifestsToKeep)) { // At least one of the manifests we want to keep does not exist. This is an error condition GLog->Log(ELogVerbosity::Error, TEXT("Not all manifests to keep exist. Aborting operation")); return false; } if (!DeleteNonReferencedManifests(ManifestFilenames, ManifestsToKeep, DeletedManifestFilenames, ManifestBytesDeleted)) { // An error occurred deleting one or more of the manifest files. This is an error condition GLog->Log(ELogVerbosity::Error, TEXT("Could not delete one or more manifest files. Aborting operation")); return false; } // If we don't have any manifest files, we'll treat that as an error condition if (ManifestFilenames.Num() == 0) { GLog->Log(ELogVerbosity::Warning, TEXT("Could not find any manifest files. Aborting operation.")); return true; // We're still going to return a success code, as this isn't a hard error } // Process each remaining manifest, and build up a list of all referenced files for (const auto& ManifestFilename : ManifestFilenames) { const FString ManifestPath = CloudDir / ManifestFilename; GLog->Logf(TEXT("Extracting chunk filenames from %s"), *ManifestFilename); // Load the manifest data from the manifest FBuildPatchAppManifest Manifest; if (Manifest.LoadFromFile(ManifestPath)) { // Work out all data Guids referenced in the manifest, and add them to our list of files to keep DataGuids.Empty(); Manifest.GetDataList(DataGuids); GLog->Logf(TEXT("Extracted %d chunks from %s. Unioning with %d existing chunks"), DataGuids.Num(), *ManifestFilename, NumDataFiles); NumDataFiles += DataGuids.Num(); // We're going to keep all the Guids so we know which files to keep later ReferencedGuids.Append(DataGuids); } else { // We failed to read from the manifest file. This is an error which should halt progress and return a non-zero exit code FString ErrorMessage = FString::Printf(TEXT("Could not parse manifest file %s"), *ManifestFilename); GLog->Log(ELogVerbosity::Error, *ErrorMessage); return false; } } GLog->Logf(TEXT("Compactify walking %s to touch referenced chunks, remove all aged unreferenced chunks and compute statistics."), *CloudDir); uint32 FilesProcessed = 0; uint32 FilesTouched = 0; uint32 FilesSkipped = 0; uint32 NonPatchFilesProcessed = 0; uint32 FilesDeleted = DeletedManifestFilenames.Num(); uint64 BytesProcessed = 0; uint64 BytesTouched = 0; uint64 BytesSkipped = 0; uint64 NonPatchBytesProcessed = 0; uint64 BytesDeleted = ManifestBytesDeleted; uint64 CurrentFileSize; uint32 TouchFailureCount = 0; uint64 BytesFailedTouch = 0; FGuid FileGuid; FDateTime Now = FDateTime::UtcNow(); const uint32 TouchFailureErrorThreshold = FMath::DivideAndRoundUp((uint32)AllFiles.Num(), BuildDataCompactifierDefs::TouchFailureThresholdPercentage); for (const auto& File : AllFiles) { CurrentFileSize = IFileManager::Get().FileSize(*File); if (CurrentFileSize >= 0) { ++FilesProcessed; BytesProcessed += CurrentFileSize; if (!GetPatchDataGuid(File, FileGuid)) { FString CleanFilename = FPaths::GetCleanFilename(File); if (!ManifestFilenames.Contains(CleanFilename) && !DeletedManifestFilenames.Contains(CleanFilename)) { ++NonPatchFilesProcessed; NonPatchBytesProcessed += CurrentFileSize; } continue; } if (ReferencedGuids.Contains(FileGuid)) { // This is a valid, referenced file, so we need to touch it with the current date ++FilesTouched; BytesTouched += CurrentFileSize; if (!bPreview) { if (!SafeSetTimeStamp(*File, Now)) { ++TouchFailureCount; BytesFailedTouch += CurrentFileSize; GLog->Logf(ELogVerbosity::Warning, TEXT("Failed to set timestamp on file %s"), *File); if (TouchFailureCount >= TouchFailureErrorThreshold) { GLog->Logf(ELogVerbosity::Error, TEXT("Failed to set timestamp on %u%% of files"), BuildDataCompactifierDefs::TouchFailureThresholdPercentage); return false; } } } } else if (IFileManager::Get().GetTimeStamp(*File) < Cutoff) { // This file is not referenced by any manifest, is a data file, and is older than we want to keep ... // Let's get rid of it! DeleteFile(File); ++FilesDeleted; BytesDeleted += CurrentFileSize; } else { ++FilesSkipped; BytesSkipped += CurrentFileSize; } } else { GLog->Logf(TEXT("Warning. Could not determine size of %s. Perhaps it has been removed by another process."), *File); } } FilesTouched -= TouchFailureCount; BytesTouched -= BytesFailedTouch; GLog->Logf(TEXT("Compactify of %s complete!"), *CloudDir); GLog->Logf(TEXT("Compactify found %u files totalling %s."), FilesProcessed, *HumanReadableSize(BytesProcessed)); if (NonPatchFilesProcessed > 0) { GLog->Logf(TEXT("Of these, %u (totalling %s) were not chunk/manifest files."), NonPatchFilesProcessed, *HumanReadableSize(NonPatchBytesProcessed)); } if (bNoPatchDelete) { GLog->Logf(TEXT("Compactify skipped deleting %u unreferenced chunk/manifest files (totalling %s) due to -nopatchdelete being specified."), FilesDeleted, *HumanReadableSize(BytesDeleted)); } else { GLog->Logf(TEXT("Compactify deleted %u chunk/manifest files totalling %s."), FilesDeleted, *HumanReadableSize(BytesDeleted)); } GLog->Logf(TEXT("Compactify touched %u chunk files totalling %s."), FilesTouched, *HumanReadableSize(BytesTouched)); if (TouchFailureCount > 0) { GLog->Logf(TEXT("Compactify failed to touch %u files toatlling %s."), TouchFailureCount, *HumanReadableSize(BytesFailedTouch)); } GLog->Logf(TEXT("Compactify skipped %u unreferenced chunk files (totalling %s) which have not yet aged out."), FilesSkipped, *HumanReadableSize(BytesSkipped)); return true; }
void FFoliageInstanceBaseCache::CompactInstanceBaseCache(AInstancedFoliageActor* IFA) { UWorld* World = IFA->GetWorld(); if (!World || World->IsGameWorld()) { return; } FFoliageInstanceBaseCache& Cache = IFA->InstanceBaseCache; TSet<FFoliageInstanceBaseId> BasesInUse; for (auto& Pair : IFA->FoliageMeshes) { for (const auto& Pair : Pair.Value->ComponentHash) { if (Pair.Key != FFoliageInstanceBaseCache::InvalidBaseId) { BasesInUse.Add(Pair.Key); } } } // Look for any removed maps TSet<FFoliageInstanceBasePtr> InvalidBasePtrs; for (auto& Pair : Cache.InstanceBaseLevelMap) { const auto& WorldAsset = Pair.Key; bool bExists = (WorldAsset == World); // Check sub-levels if (!bExists) { const FName PackageName = FName(*FPackageName::ObjectPathToPackageName(WorldAsset.ToStringReference().ToString())); if (World->WorldComposition) { bExists = World->WorldComposition->DoesTileExists(PackageName); } else { bExists = (World->GetLevelStreamingForPackageName(PackageName) != nullptr); } } if (!bExists) { InvalidBasePtrs.Append(Pair.Value); Cache.InstanceBaseLevelMap.Remove(Pair.Key); } else { // Remove dead links for (int32 i = Pair.Value.Num()-1; i >= 0; --i) { // Base needs to be removed if it's not in use by existing instances or component was removed if (Pair.Value[i].IsNull() || !BasesInUse.Contains(Cache.GetInstanceBaseId(Pair.Value[i]))) { InvalidBasePtrs.Add(Pair.Value[i]); Pair.Value.RemoveAt(i); } } if (Pair.Value.Num() == 0) { Cache.InstanceBaseLevelMap.Remove(Pair.Key); } } } TSet<FFoliageInstanceBaseId> InvalidBaseIds; Cache.InstanceBaseInvMap.Empty(); // Look for any removed base components for (const auto& Pair : Cache.InstanceBaseMap) { const FFoliageInstanceBaseInfo& BaseInfo = Pair.Value; if (InvalidBasePtrs.Contains(BaseInfo.BasePtr)) { InvalidBaseIds.Add(Pair.Key); Cache.InstanceBaseMap.Remove(Pair.Key); } else { // Regenerate inverse map check(!Cache.InstanceBaseInvMap.Contains(BaseInfo.BasePtr)); Cache.InstanceBaseInvMap.Add(BaseInfo.BasePtr, Pair.Key); } } if (InvalidBaseIds.Num()) { for (auto& Pair : IFA->FoliageMeshes) { auto& MeshInfo = Pair.Value; MeshInfo->ComponentHash.Empty(); int32 InstanceIdx = 0; for (FFoliageInstance& Instance : MeshInfo->Instances) { if (InvalidBaseIds.Contains(Instance.BaseId)) { Instance.BaseId = FFoliageInstanceBaseCache::InvalidBaseId; } MeshInfo->ComponentHash.FindOrAdd(Instance.BaseId).Add(InstanceIdx); InstanceIdx++; } } Cache.InstanceBaseMap.Compact(); Cache.InstanceBaseLevelMap.Compact(); } }