/** * Debug spew for components * @param Object object to dump component spew for */ void DumpComponents(UObject *Object) { for ( FObjectIterator It; It; ++It ) { It->UnMark(EObjectMark(OBJECTMARK_TagImp | OBJECTMARK_TagExp)); } if (FPlatformMisc::IsDebuggerPresent() ) { // if we have a debugger attached, the watch window won't be able to display the full output if we attempt to log it as a single string // so pass in GLog instead so that each line is sent separately; this causes the output to have an extra line break between each log statement, // but at least we'll be able to see the full output in the debugger's watch window UE_LOG(LogExporter, Log, TEXT("Components for '%s':"), *Object->GetFullName()); ExportProperties( NULL, *GLog, Object->GetClass(), (uint8*)Object, 0, NULL, NULL, Object, PPF_SubobjectsOnly ); UE_LOG(LogExporter, Log, TEXT("<--- DONE!")); } else { FStringOutputDevice Output; Output.Logf(TEXT("Components for '%s':\r\n"), *Object->GetFullName()); ExportProperties( NULL, Output, Object->GetClass(), (uint8*)Object, 2, NULL, NULL, Object, PPF_SubobjectsOnly ); Output.Logf(TEXT("<--- DONE!\r\n")); UE_LOG(LogExporter, Log, TEXT("%s"), *Output); } }
/** * Returns a list of all assets referenced by the specified UObject. */ void FFindReferencedAssets::BuildAssetList(UObject *Object, const TArray<UClass*>& IgnoreClasses, const TArray<UObject*>& IgnorePackages, TSet<UObject*>& ReferencedAssets, bool bIncludeDefaultRefs) { TArray<FReferencedAssets> LocalReferencers; // Create a new entry for this actor. new( LocalReferencers ) FReferencedAssets( Object ); for (FObjectIterator It; It; ++It) { // Skip the level, world, and any packages that should be ignored if ( ShouldSearchForAssets(*It, IgnoreClasses, IgnorePackages, bIncludeDefaultRefs) ) { It->Mark(OBJECTMARK_TagExp); } else { It->UnMark(OBJECTMARK_TagExp); } } // Add to the list of referenced assets. FFindAssetsArchive( Object, LocalReferencers.Last().AssetList, NULL, /*MaxRecursion=*/0, /*bIncludeClasses=*/true, bIncludeDefaultRefs ); ReferencedAssets = LocalReferencers.Last().AssetList; }
bool UOnlineHotfixManager::HotfixPakIniFile(const FString& FileName) { FConfigFile* ConfigFile = GetConfigFile(FileName); ConfigFile->Combine(FileName); UE_LOG(LogHotfixManager, Log, TEXT("Hotfix merged INI (%s) found in a PAK file"), *FileName); FName IniFileName(*FileName, FNAME_Find); int32 NumObjectsReloaded = 0; const double StartTime = FPlatformTime::Seconds(); // Now that we have a list of classes to update, we can iterate objects and // reload if they match the INI file that was changed for (FObjectIterator It; It; ++It) { UClass* Class = It->GetClass(); if (Class->HasAnyClassFlags(CLASS_Config) && Class->ClassConfigName == IniFileName) { // Force a reload of the config vars It->ReloadConfig(); NumObjectsReloaded++; } } UE_LOG(LogHotfixManager, Log, TEXT("Updating config from %s took %f seconds reloading %d objects"), *FileName, FPlatformTime::Seconds() - StartTime, NumObjectsReloaded); return true; }
void MarkAllObjects() { // Mark all objects so we don't get into an endless recursion for (FObjectIterator It; It; ++It) { It->Mark(OBJECTMARK_TagExp); } }
// This is from FArchiveTraceRoute -This only creates object graph of all objects // This can be used by other classes such as FTraceReferences - trace references of one object FArchiveObjectGraph::FArchiveObjectGraph(bool IncludeTransients, EObjectFlags KeepFlags) : CurrentReferencer(NULL), bIncludeTransients(IncludeTransients), RequiredFlags(KeepFlags) { ArIsObjectReferenceCollector = true; // ALL objects reference their outers...it's just log spam here //ArIgnoreOuterRef = true; TArray<UObject*> RootObjects; // allocate enough memory for all objects ObjectGraph.Empty(GetUObjectArray().GetObjectArrayNum()); RootObjects.Empty(GetUObjectArray().GetObjectArrayNum()); // search for objects that have the right flags and add them to the list of objects that we're going to start with // all other objects need to be tagged so that we can tell whether they've been serialized or not. for( FObjectIterator It; It; ++It ) { UObject* CurrentObject = *It; if ( CurrentObject->HasAnyFlags(RequiredFlags) ) { // make sure it isn't tagged // ASKRON: WHY do we need this? CurrentObject->UnMark(OBJECTMARK_TagExp); RootObjects.Add(CurrentObject); ObjectGraph.Add(CurrentObject, new FObjectGraphNode(CurrentObject)); } else { // ASKRON: WHY do we need this? CurrentObject->Mark(OBJECTMARK_TagExp); } } // Populate the ObjectGraph - this serializes our root set to map out the relationships between all rooted objects GenerateObjectGraph(RootObjects); // we won't be adding any additional objects for the arrays and graphs, so free up any memory not being used. RootObjects.Shrink(); ObjectGraph.Shrink(); // we're done with serialization; clear the tags so that we don't interfere with anything else for( FObjectIterator It; It; ++It ) { It->UnMark(OBJECTMARK_TagExp); } }
void UUnrealEdEngine::GetPackageList( TArray<UPackage*>* InPackages, UClass* InClass ) { InPackages->Empty(); for( FObjectIterator It ; It ; ++It ) { if( It->GetOuter() && It->GetOuter() != GetTransientPackage() ) { UObject* TopParent = NULL; if( InClass == NULL || It->IsA( InClass ) ) TopParent = It->GetOutermost(); if( Cast<UPackage>(TopParent) ) InPackages->AddUnique( (UPackage*)TopParent ); } } }
/** * prefilter shader for suspension raycasts */ static PxQueryHitType::Enum WheelRaycastPreFilter( PxFilterData SuspensionData, PxFilterData HitData, const void* constantBlock, PxU32 constantBlockSize, PxHitFlags& filterFlags) { // SuspensionData is the vehicle suspension raycast. // HitData is the shape potentially hit by the raycast. // don't collide with owner chassis if ( SuspensionData.word0 == HitData.word0 ) { return PxSceneQueryHitType::eNONE; } // collision channels filter ECollisionChannel SuspensionChannel = (ECollisionChannel)(SuspensionData.word3 >> 24); if ( ECC_TO_BITFIELD(SuspensionChannel) & HitData.word1) { // debug what object we hit if ( false ) { for ( FObjectIterator It; It; ++It ) { if ( It->GetUniqueID() == HitData.word0 ) { UObject* HitObj = *It; FString HitObjName = HitObj->GetName(); break; } } } return PxSceneQueryHitType::eBLOCK; } return PxSceneQueryHitType::eNONE; }
void FEnumEditorUtils::BroadcastChanges(const UUserDefinedEnum* Enum, const TArray<TPair<FName, int8>>& OldNames, bool bResolveData) { check(NULL != Enum); if (bResolveData) { FArchiveEnumeratorResolver EnumeratorResolver(Enum, OldNames); TArray<UClass*> ClassesToCheck; for (TObjectIterator<UByteProperty> PropertyIter; PropertyIter; ++PropertyIter) { const UByteProperty* ByteProperty = *PropertyIter; if (ByteProperty && (Enum == ByteProperty->GetIntPropertyEnum())) { UClass* OwnerClass = ByteProperty->GetOwnerClass(); if (OwnerClass) { ClassesToCheck.Add(OwnerClass); } } } for (FObjectIterator ObjIter; ObjIter; ++ObjIter) { for (auto ClassIter = ClassesToCheck.CreateConstIterator(); ClassIter; ++ClassIter) { if (ObjIter->IsA(*ClassIter)) { ObjIter->Serialize(EnumeratorResolver); break; } } } } struct FNodeValidatorHelper { static bool IsValid(UK2Node* Node) { return Node && (NULL != Cast<UEdGraph>(Node->GetOuter())) && !Node->HasAnyFlags(RF_Transient | RF_PendingKill); } }; TSet<UBlueprint*> BlueprintsToRefresh; { //CUSTOM NODES DEPENTENT ON ENUM for (TObjectIterator<UK2Node> It(RF_Transient); It; ++It) { UK2Node* Node = *It; INodeDependingOnEnumInterface* NodeDependingOnEnum = Cast<INodeDependingOnEnumInterface>(Node); if (FNodeValidatorHelper::IsValid(Node) && NodeDependingOnEnum && (Enum == NodeDependingOnEnum->GetEnum())) { if (UBlueprint* Blueprint = Node->GetBlueprint()) { if (NodeDependingOnEnum->ShouldBeReconstructedAfterEnumChanged()) { Node->ReconstructNode(); } BlueprintsToRefresh.Add(Blueprint); } } } } for (TObjectIterator<UEdGraphPin> It(RF_Transient); It; ++It) { UEdGraphPin* Pin = *It; if (Pin && (Enum == Pin->PinType.PinSubCategoryObject.Get()) && (EEdGraphPinDirection::EGPD_Input == Pin->Direction)) { UK2Node* Node = Cast<UK2Node>(Pin->GetOuter()); if (FNodeValidatorHelper::IsValid(Node)) { if (UBlueprint* Blueprint = Node->GetBlueprint()) { if (INDEX_NONE == Enum->FindEnumIndex(*Pin->DefaultValue)) { Pin->Modify(); if (Blueprint->BlueprintType == BPTYPE_Interface) { Pin->DefaultValue = Enum->GetEnumName(0); } else { Pin->DefaultValue = FEnumEditorUtilsHelper::InvalidName(); } Node->PinDefaultValueChanged(Pin); BlueprintsToRefresh.Add(Blueprint); } } } } } for (auto It = BlueprintsToRefresh.CreateIterator(); It; ++It) { FBlueprintEditorUtils::MarkBlueprintAsModified(*It); (*It)->BroadcastChanged(); } FEnumEditorManager::Get().PostChange(Enum, EEnumEditorChangeInfo::Changed); }
/** * Builds a list of assets to display from the currently selected actors. * NOTE: It ignores assets that are there because they are always loaded * such as default materials, textures, etc. */ void BuildAssetList(UWorld* InWorld, int32 Depth, bool bShowDefault, bool bShowScript) { // Clear the old list Referencers.Empty(); ReferenceGraph.Empty(); ObjectNameCache.Empty(); TArray<UObject*> BspMats; // Search all BSP surfaces for ones that are selected and add their // Materials to a temp list for (int32 Index = 0; Index < InWorld->GetModel()->Surfs.Num(); Index++) { // Only add materials that are selected if (InWorld->GetModel()->Surfs[Index].PolyFlags & PF_Selected) { // No point showing the default material if (InWorld->GetModel()->Surfs[Index].Material != NULL) { BspMats.AddUnique(InWorld->GetModel()->Surfs[Index].Material); } } } // If any BSP surfaces are selected if (BspMats.Num() > 0) { FReferencedAssets* Referencer = new(Referencers) FReferencedAssets(InWorld->GetModel()); // Now copy the array Referencer->AssetList = BspMats; ReferenceGraph.Add(InWorld->GetModel(), BspMats); } // This is the maximum depth to use when searching for references const int32 MaxRecursionDepth = Depth; USelection* ActorSelection = GEditor->GetSelectedActors(); // Mark all objects so we don't get into an endless recursion for (FObjectIterator It; It; ++It) { // Skip the level, world, and any packages that should be ignored if (ShouldSearchForAssets(*It, IgnoreClasses, IgnorePackages, bShowDefault)) { It->Mark(OBJECTMARK_TagExp); } else { It->UnMark(OBJECTMARK_TagExp); } } TArray<AActor*> SelectedActors; // Get the list of currently selected actors ActorSelection->GetSelectedObjects<AActor>(SelectedActors); // Build the list of assets from the set of selected actors for (int32 Index = 0; Index < SelectedActors.Num(); Index++) { // Set the flag for the selected item, as it could have actually been cleared by an // earlier selected object, which would result in a crash later SelectedActors[Index]->Mark(OBJECTMARK_TagExp); // Create a new entry for this actor FReferencedAssets* Referencer = new(Referencers) FReferencedAssets(SelectedActors[Index]); // Add to the list of referenced assets FFindAssetsArchive(SelectedActors[Index], Referencer->AssetList, &ReferenceGraph, MaxRecursionDepth, bShowScript, bShowDefault); } // Rebuild the name cache for (int32 RefIndex = 0; RefIndex < Referencers.Num(); RefIndex++) { FReferencedAssets& Referencer = Referencers[RefIndex]; if (!ObjectNameCache.Contains(Referencer.Referencer)) { ObjectNameCache.Add(Referencer.Referencer, *Referencer.Referencer->GetName()); } for (int32 AssetIndex = 0; AssetIndex < Referencer.AssetList.Num(); AssetIndex++) { if (!ObjectNameCache.Contains(Referencer.AssetList[AssetIndex])) { ObjectNameCache.Add(Referencer.AssetList[AssetIndex], *Referencer.AssetList[AssetIndex]->GetName()); } } } }
int32 UGenerateDistillFileSetsCommandlet::Main( const FString& InParams ) { // Parse command line. TArray<FString> Tokens; TArray<FString> Switches; UCommandlet::ParseCommandLine(*InParams, Tokens, Switches); TArray<FString> MapList; for ( int32 MapIdx = 0; MapIdx < Tokens.Num(); ++MapIdx ) { const FString& Map = Tokens[MapIdx]; if ( FPackageName::IsShortPackageName(Map) ) { FString LongPackageName; if ( FPackageName::SearchForPackageOnDisk(Map, &LongPackageName) ) { MapList.Add(LongPackageName); } else { UE_LOG(LogGenerateDistillFileSetsCommandlet, Error, TEXT("Unable to find package for map %s."), *Map); return 1; } } else { MapList.Add(Map); } } if ( MapList.Num() <= 0 ) { // No map tokens were supplied on the command line, so assume all maps TArray<FString> AllPackageFilenames; FEditorFileUtils::FindAllPackageFiles(AllPackageFilenames); for (int32 PackageIndex = 0; PackageIndex < AllPackageFilenames.Num(); PackageIndex++) { const FString& Filename = AllPackageFilenames[PackageIndex]; if (FPaths::GetExtension(Filename, true) == FPackageName::GetMapPackageExtension() ) { FString LongPackageName; if ( FPackageName::TryConvertFilenameToLongPackageName(Filename, LongPackageName) ) { // Warn about maps in "NoShip" or "TestMaps" folders. Those should have been filtered out during the Distill process! if( !Filename.Contains( "/NoShip/") && !Filename.Contains( "/TestMaps/")) { // @todo plugins add support for plugins? if ( LongPackageName.StartsWith(TEXT("/Game")) ) { UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT( "Discovered map package %s..." ), *LongPackageName ); MapList.Add(LongPackageName); } } else { UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT("Skipping map package %s in TestMaps or NoShip folder"), *Filename); } } else { UE_LOG(LogGenerateDistillFileSetsCommandlet, Warning, TEXT("Failed to determine package name for map file %s."), *Filename); } } } } const FString TemplateFileSwitch = TEXT("Template="); const FString OutputFileSwitch = TEXT("Output="); const FString TemplateFolderSwitch = TEXT("TemplateFolder="); const FString OutputFolderSwitch = TEXT("OutputFolder="); FString TemplateFilename; FString OutputFilename; FString TemplateFolder; FString OutputFolder; for (int32 SwitchIdx = 0; SwitchIdx < Switches.Num(); ++SwitchIdx) { const FString& Switch = Switches[SwitchIdx]; if ( Switch.StartsWith(TemplateFileSwitch) ) { Switch.Split(TEXT("="), NULL, &TemplateFilename); } else if ( Switch.StartsWith(OutputFileSwitch) ) { Switch.Split(TEXT("="), NULL, &OutputFilename); } else if ( Switch.StartsWith(TemplateFolderSwitch) ) { Switch.Split(TEXT("="), NULL, &TemplateFolder); FPaths::NormalizeFilename(TemplateFolder); if ( !TemplateFolder.EndsWith(TEXT("/")) ) { TemplateFolder += TEXT("/"); } UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT("Using template folder: "), *TemplateFolder); } else if ( Switch.StartsWith(OutputFolderSwitch) ) { Switch.Split(TEXT("="), NULL, &OutputFolder); FPaths::NormalizeFilename(OutputFolder); if ( !OutputFolder.EndsWith(TEXT("/")) ) { OutputFolder += TEXT("/"); } UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT("Using output folder: "), *OutputFolder); } } if ( TemplateFilename.IsEmpty() || OutputFilename.IsEmpty() ) { UE_LOG(LogGenerateDistillFileSetsCommandlet, Error, TEXT("You must supply a -Template=TemplateFilename and -Output=OutputFilename. These files are relative to the Game/Build directory.")); return 1; } // If no folder was specified, filenames are relative to the build dir. if ( TemplateFolder.IsEmpty() ) { TemplateFolder = FPaths::GameDir() + TEXT("Build/"); } if ( OutputFolder.IsEmpty() ) { OutputFolder = FPaths::GameDir() + TEXT("Build/"); } TemplateFilename = TemplateFolder + TemplateFilename; OutputFilename = OutputFolder + OutputFilename; // Load the template file FString TemplateFileContents; if ( !FFileHelper::LoadFileToString(TemplateFileContents, *TemplateFilename) ) { UE_LOG(LogGenerateDistillFileSetsCommandlet, Error, TEXT("Failed to load template file '%s'"), *TemplateFilename); return 1; } // Form a full unique package list TSet<FString> AllPackageNames; //@todo SLATE: This is a hack to ensure all slate referenced assets get cooked. // Slate needs to be refactored to properly identify required assets at cook time. // Simply jamming everything in a given directory into the cook list is error-prone // on many levels - assets not required getting cooked/shipped; assets not put under // the correct folder; etc. { TArray<FString> UIContentPaths; if (GConfig->GetArray(TEXT("UI"), TEXT("ContentDirectories"), UIContentPaths, GEditorIni) > 0) { for (int32 DirIdx = 0; DirIdx < UIContentPaths.Num(); DirIdx++) { FString ContentPath = FPackageName::LongPackageNameToFilename(UIContentPaths[DirIdx]); TArray<FString> Files; IFileManager::Get().FindFilesRecursive(Files, *ContentPath, *(FString(TEXT("*")) + FPackageName::GetAssetPackageExtension()), true, false); for (int32 Index = 0; Index < Files.Num(); Index++) { FString StdFile = Files[Index]; FPaths::MakeStandardFilename(StdFile); StdFile = FPackageName::FilenameToLongPackageName(StdFile); AllPackageNames.Add(StdFile); } } } } // Load all maps { for ( auto MapIt = MapList.CreateConstIterator(); MapIt; ++MapIt ) { const FString& MapPackage = *MapIt; UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT( "Loading %s..." ), *MapPackage ); UPackage* Package = LoadPackage( NULL, *MapPackage, LOAD_None ); if( Package != NULL ) { AllPackageNames.Add(Package->GetName()); UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT( "Finding content referenced by %s..." ), *MapPackage ); for ( FObjectIterator ObjIt; ObjIt; ++ObjIt ) { AllPackageNames.Add(ObjIt->GetOutermost()->GetName()); } UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT( "Collecting garbage..." ) ); CollectGarbage(RF_Native); } } } // Sort the results to make it easier to diff files. No necessary but useful sometimes. TArray<FString> SortedPackageNames = AllPackageNames.Array(); SortedPackageNames.Sort(); // For the list of FileSets to include in the distill FString AllFileSets; const FString FileSetPathRoot = TEXT("Content"); for (auto PackageIt = SortedPackageNames.CreateConstIterator(); PackageIt; ++PackageIt) { const FString& PackageName = *PackageIt; // @todo plugins add support for plugins? if ( PackageName.StartsWith(TEXT("/Game")) ) { const FString PathWithoutRoot( PackageName.Mid( 5 ) ); const FString FileSetPath = FileSetPathRoot + PathWithoutRoot; AllFileSets += FString::Printf( TEXT("<FileSet Path=\"%s.*\" bIsRecursive=\"false\"/>") LINE_TERMINATOR, *FileSetPath ); } } // Write the output file FString OutputFileContents = TemplateFileContents.Replace(TEXT("%INSTALLEDCONTENTFILESETS%"), *AllFileSets, ESearchCase::CaseSensitive); if ( FApp::HasGameName() ) { UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT( "Replacing %%GAMENAME%% with (%s)..." ), FApp::GetGameName() ); OutputFileContents = OutputFileContents.Replace(TEXT("%GAMENAME%"), FApp::GetGameName(), ESearchCase::CaseSensitive); } else { UE_LOG(LogGenerateDistillFileSetsCommandlet, Warning, TEXT("Failed to replace %%GAMENAME%% since we are running without a game name.")); } if ( !FFileHelper::SaveStringToFile(OutputFileContents, *OutputFilename) ) { UE_LOG(LogGenerateDistillFileSetsCommandlet, Error, TEXT("Failed to save output file '%s'"), *OutputFilename); return 1; } return 0; }
bool EngineUtils::FindOrLoadAssetsByPath(const FString& Path, TArray<UObject*>& OutAssets) { if ( !FPackageName::IsValidLongPackageName(Path, true) ) { return false; } // Convert the package path to a filename with no extension (directory) const FString FilePath = FPackageName::LongPackageNameToFilename(Path); // Gather the package files in that directory and subdirectories TArray<FString> Filenames; FPackageName::FindPackagesInDirectory(Filenames, FilePath); // Cull out map files for (int32 FilenameIdx = Filenames.Num() - 1; FilenameIdx >= 0; --FilenameIdx) { const FString Extension = FPaths::GetExtension(Filenames[FilenameIdx], true); if ( Extension == FPackageName::GetMapPackageExtension() ) { Filenames.RemoveAt(FilenameIdx); } } // Load packages or find existing ones and fully load them TSet<UPackage*> Packages; for (int32 FileIdx = 0; FileIdx < Filenames.Num(); ++FileIdx) { const FString& Filename = Filenames[FileIdx]; UPackage* Package = FindPackage(NULL, *FPackageName::FilenameToLongPackageName(Filename)); if (Package) { Package->FullyLoad(); } else { Package = LoadPackage(NULL, *Filename, LOAD_None); } if (Package) { Packages.Add(Package); } } // If any packages were successfully loaded, find all assets that were in the packages and add them to OutAssets if ( Packages.Num() > 0 ) { for (FObjectIterator ObjIt; ObjIt; ++ObjIt) { if ( Packages.Contains(ObjIt->GetOutermost()) && ObjIt->IsAsset() ) { OutAssets.Add(*ObjIt); } } } return true; }
bool UOnlineHotfixManager::HotfixIniFile(const FString& FileName, const FString& IniData) { FConfigFile* ConfigFile = GetConfigFile(FileName); // Merge the string into the config file ConfigFile->CombineFromBuffer(IniData); TArray<UClass*> Classes; TArray<UObject*> PerObjectConfigObjects; int32 StartIndex = 0; int32 EndIndex = 0; // Find the set of object classes that were affected while (StartIndex >= 0 && StartIndex < IniData.Len() && EndIndex >= StartIndex) { // Find the next section header StartIndex = IniData.Find(TEXT("["), ESearchCase::IgnoreCase, ESearchDir::FromStart, StartIndex); if (StartIndex > -1) { // Find the ending section identifier EndIndex = IniData.Find(TEXT("]"), ESearchCase::IgnoreCase, ESearchDir::FromStart, StartIndex); if (EndIndex > StartIndex) { int32 PerObjectNameIndex = IniData.Find(TEXT(" "), ESearchCase::IgnoreCase, ESearchDir::FromStart, StartIndex); // Per object config entries will have a space in the name, but classes won't if (PerObjectNameIndex == -1 || PerObjectNameIndex > EndIndex) { if (IniData.StartsWith(TEXT("[/Script/"), ESearchCase::IgnoreCase)) { const int32 ScriptSectionTag = 9; // Snip the text out and try to find the class for that const FString PackageClassName = IniData.Mid(StartIndex + ScriptSectionTag, EndIndex - StartIndex - ScriptSectionTag); // Find the class for this so we know what to update UClass* Class = FindObject<UClass>(nullptr, *PackageClassName, true); if (Class) { // Add this to the list to check against Classes.Add(Class); } } } // Handle the per object config case by finding the object for reload else { const int32 Count = PerObjectNameIndex - StartIndex - 1; const FString PerObjectName = IniData.Mid(StartIndex + 1, Count); // Explicitly search the transient package (won't update non-transient objects) UObject* PerObject = FindObject<UObject>(ANY_PACKAGE, *PerObjectName, false); if (PerObject != nullptr) { PerObjectConfigObjects.Add(PerObject); } } StartIndex = EndIndex; } } } int32 NumObjectsReloaded = 0; const double StartTime = FPlatformTime::Seconds(); if (Classes.Num()) { // Now that we have a list of classes to update, we can iterate objects and reload for (FObjectIterator It; It; ++It) { UClass* Class = It->GetClass(); if (Class->HasAnyClassFlags(CLASS_Config)) { // Check to see if this class is in our list (yes, potentially n^2, but not in practice) for (int32 ClassIndex = 0; ClassIndex < Classes.Num(); ClassIndex++) { if (It->IsA(Classes[ClassIndex])) { // Force a reload of the config vars It->ReloadConfig(); NumObjectsReloaded++; break; } } } } } // Reload any PerObjectConfig objects that were affected for (auto ReloadObject : PerObjectConfigObjects) { ReloadObject->ReloadConfig(); NumObjectsReloaded++; } UE_LOG(LogHotfixManager, Log, TEXT("Updating config from %s took %f seconds and reloaded %d objects"), *FileName, FPlatformTime::Seconds() - StartTime, NumObjectsReloaded); return true; }