void UK2Node_AddComponent::PostPasteNode() { Super::PostPasteNode(); // There is a template associated with this node that should be unique, but after a node is pasted, it either points to a // template shared by the copied node, or to nothing (when pasting into a different blueprint) const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>(); UBlueprint* Blueprint = GetBlueprint(); // Find the template name and return type pins UEdGraphPin* TemplateNamePin = GetTemplateNamePin(); UEdGraphPin* ReturnPin = GetReturnValuePin(); if ((TemplateNamePin != NULL) && (ReturnPin != NULL)) { // Find the current template if it exists FString TemplateName = TemplateNamePin->DefaultValue; UActorComponent* SourceTemplate = Blueprint->FindTemplateByName(FName(*TemplateName)); // Determine the type of the component needed UClass* ComponentClass = Cast<UClass>(ReturnPin->PinType.PinSubCategoryObject.Get()); if (ComponentClass) { ensure(NULL != Cast<UBlueprintGeneratedClass>(Blueprint->GeneratedClass)); // Create a new template object and update the template pin to point to it UActorComponent* NewTemplate = NewObject<UActorComponent>(Blueprint->GeneratedClass, ComponentClass, NAME_None, RF_ArchetypeObject|RF_Public); Blueprint->ComponentTemplates.Add(NewTemplate); TemplateNamePin->DefaultValue = NewTemplate->GetName(); // Copy the old template data over to the new template if it's compatible if ((SourceTemplate != NULL) && (SourceTemplate->GetClass()->IsChildOf(ComponentClass))) { TArray<uint8> SavedProperties; FObjectWriter Writer(SourceTemplate, SavedProperties); FObjectReader(NewTemplate, SavedProperties); } else if(TemplateBlueprint.Len() > 0) { // try to find/load our blueprint to copy the template UBlueprint* SourceBlueprint = FindObject<UBlueprint>(NULL, *TemplateBlueprint); if(SourceBlueprint != NULL) { SourceTemplate = SourceBlueprint->FindTemplateByName(FName(*TemplateName)); if ((SourceTemplate != NULL) && (SourceTemplate->GetClass()->IsChildOf(ComponentClass))) { TArray<uint8> SavedProperties; FObjectWriter Writer(SourceTemplate, SavedProperties); FObjectReader(NewTemplate, SavedProperties); } } TemplateBlueprint.Empty(); } } else { // Clear the template connection; can't resolve the type of the component to create ensure(false); TemplateNamePin->DefaultValue = TEXT(""); } } }
/** * Runs compile-on-load test against all unloaded, and optionally loaded, blueprints * See the TestAllBlueprints config key in the [Automation.Blueprint] config sections */ bool FBlueprintCompileOnLoadTest::RunTest(const FString& BlueprintAssetPath) { FCompilerResultsLog Results; UBlueprint* ExistingBP = nullptr; // if this blueprint was already loaded, then these tests are invalidated // (because dependencies have already been loaded) if (FBlueprintAutomationTestUtilities::IsBlueprintLoaded(BlueprintAssetPath, &ExistingBP)) { if (FBlueprintAutomationTestUtilities::IsAssetUnsaved(BlueprintAssetPath)) { AddError(FString::Printf(TEXT("You have unsaved changes made to '%s', please save them before running this test."), *BlueprintAssetPath)); return false; } else { AddWarning(FString::Printf(TEXT("Test may be invalid (the blueprint is already loaded): '%s'"), *BlueprintAssetPath)); FBlueprintAutomationTestUtilities::UnloadBlueprint(ExistingBP); } } // tracks blueprints that were already loaded (and cleans up any that were // loaded in its lifetime, once it is destroyed) FScopedBlueprintUnloader NewBlueprintUnloader(/*bAutoOpenScope =*/true, /*bRunGCOnCloseIn =*/true); // We load the blueprint twice and compare the two for discrepancies. This is // to bring dependency load issues to light (among other things). If a blueprint's // dependencies are loaded too late, then this first object is the degenerate one. UBlueprint* InitialBlueprint = Cast<UBlueprint>(StaticLoadObject(UBlueprint::StaticClass(), NULL, *BlueprintAssetPath)); // if we failed to load it the first time, then there is no need to make a // second attempt, leave them to fix up this issue first if (InitialBlueprint == NULL) { AddError(*FString::Printf(TEXT("Unable to load blueprint for: '%s'"), *BlueprintAssetPath)); return false; } if (!InitialBlueprint->SkeletonGeneratedClass || !InitialBlueprint->GeneratedClass) { AddError(*FString::Printf(TEXT("Unable to load blueprint for: '%s'. Probably it derives from an invalid class."), *BlueprintAssetPath)); return false; } // GATHER SUBOBJECTS TArray<TWeakObjectPtr<UObject>> InitialBlueprintSubobjects; { TArray<UObject*> InitialBlueprintSubobjectsPtr; GetObjectsWithOuter(InitialBlueprint, InitialBlueprintSubobjectsPtr); for (auto Obj : InitialBlueprintSubobjectsPtr) { InitialBlueprintSubobjects.Add(Obj); } } // GATHER DEPENDENCIES TSet<TWeakObjectPtr<UBlueprint>> BlueprintDependencies; { TArray<UBlueprint*> DependentBlueprints; FBlueprintEditorUtils::GetDependentBlueprints(InitialBlueprint, DependentBlueprints); for (auto BP : DependentBlueprints) { BlueprintDependencies.Add(BP); } } BlueprintDependencies.Add(InitialBlueprint); // GATHER DEPENDENCIES PERSISTENT DATA struct FReplaceInnerData { TWeakObjectPtr<UClass> Class; FStringAssetReference BlueprintAsset; }; TArray<FReplaceInnerData> ReplaceInnerData; for (auto BPToUnloadWP : BlueprintDependencies) { auto BPToUnload = BPToUnloadWP.Get(); auto OldClass = BPToUnload ? *BPToUnload->GeneratedClass : NULL; if (OldClass) { FReplaceInnerData Data; Data.Class = OldClass; Data.BlueprintAsset = FStringAssetReference(BPToUnload); ReplaceInnerData.Add(Data); } } // store off data for the initial blueprint so we can unload it (and reconstruct // later to compare it with a second one) TArray<uint8> InitialLoadData; FObjectWriter(InitialBlueprint, InitialLoadData); // grab the name before we unload the blueprint FName const BlueprintName = InitialBlueprint->GetFName(); // unload the blueprint so we can reload it (to catch any differences, now // that all its dependencies should be loaded as well) //UNLOAD DEPENDENCIES, all circular dependencies will be loaded again // unload the blueprint so we can reload it (to catch any differences, now // that all its dependencies should be loaded as well) for (auto BPToUnloadWP : BlueprintDependencies) { if (auto BPToUnload = BPToUnloadWP.Get()) { FBlueprintAutomationTestUtilities::UnloadBlueprint(BPToUnload); } } // this blueprint is now dead (will be destroyed next garbage-collection pass) UBlueprint* UnloadedBlueprint = InitialBlueprint; InitialBlueprint = NULL; // load the blueprint a second time; if the two separately loaded blueprints // are different, then this one is most likely the choice one (it has all its // dependencies loaded) UBlueprint* ReloadedBlueprint = Cast<UBlueprint>(StaticLoadObject(UBlueprint::StaticClass(), NULL, *BlueprintAssetPath)); UPackage* TransientPackage = GetTransientPackage(); FName ReconstructedName = MakeUniqueObjectName(TransientPackage, UBlueprint::StaticClass(), BlueprintName); // reconstruct the initial blueprint (using the serialized data from its initial load) EObjectFlags const StandardBlueprintFlags = RF_Public | RF_Standalone | RF_Transactional; InitialBlueprint = ConstructObject<UBlueprint>(UBlueprint::StaticClass(), TransientPackage, ReconstructedName, StandardBlueprintFlags | RF_Transient); FObjectReader(InitialBlueprint, InitialLoadData); { TMap<UObject*, UObject*> ClassRedirects; for (auto& Data : ReplaceInnerData) { UClass* OriginalClass = Data.Class.Get(); UBlueprint* NewBlueprint = Cast<UBlueprint>(Data.BlueprintAsset.ResolveObject()); UClass* NewClass = NewBlueprint ? *NewBlueprint->GeneratedClass : NULL; if (OriginalClass && NewClass) { ClassRedirects.Add(OriginalClass, NewClass); } } // REPLACE OLD DATA FArchiveReplaceObjectRef<UObject>(InitialBlueprint, ClassRedirects, /*bNullPrivateRefs=*/false, /*bIgnoreOuterRef=*/true, /*bIgnoreArchetypeRef=*/false); for (auto SubobjWP : InitialBlueprintSubobjects) { if (auto Subobj = SubobjWP.Get()) { FArchiveReplaceObjectRef<UObject>(Subobj, ClassRedirects, /*bNullPrivateRefs=*/false, /*bIgnoreOuterRef=*/true, /*bIgnoreArchetypeRef=*/false); } } UPackage* AssetPackage = ReloadedBlueprint->GetOutermost(); bool bHasUnsavedChanges = AssetPackage->IsDirty(); FBlueprintEditorUtils::RefreshAllNodes(ReloadedBlueprint); AssetPackage->SetDirtyFlag(bHasUnsavedChanges); } // look for diffs between subsequent loads and log them as errors TArray<FDiffSingleResult> BlueprintDiffs; bool bDiffsFound = FBlueprintAutomationTestUtilities::DiffBlueprints(InitialBlueprint, ReloadedBlueprint, BlueprintDiffs); if (bDiffsFound) { FBlueprintAutomationTestUtilities::ResolveCircularDependencyDiffs(ReloadedBlueprint, BlueprintDiffs); // if there are still diffs after resolving any the could have been from unloaded circular dependencies if (BlueprintDiffs.Num() > 0) { AddError(FString::Printf(TEXT("Inconsistencies between subsequent blueprint loads for: '%s' (was a dependency not preloaded?)"), *BlueprintAssetPath)); } else { bDiffsFound = false; } // list all the differences (so as to help identify what dependency was missing) for (auto DiffIt(BlueprintDiffs.CreateIterator()); DiffIt; ++DiffIt) { // will be presented in the context of "what changed between the initial load and the second?" FString DiffDescription = DiffIt->ToolTip; if (DiffDescription != DiffIt->DisplayString) { DiffDescription = FString::Printf(TEXT("%s (%s)"), *DiffDescription, *DiffIt->DisplayString); } const UEdGraphNode* NodeFromPin = DiffIt->Pin1 ? Cast<const UEdGraphNode>(DiffIt->Pin1->GetOuter()) : NULL; const UEdGraphNode* Node = DiffIt->Node1 ? DiffIt->Node1 : NodeFromPin; const UEdGraph* Graph = Node ? Node->GetGraph() : NULL; const FString GraphName = Graph ? Graph->GetName() : FString(TEXT("Unknown Graph")); AddError(FString::Printf(TEXT("%s.%s differs between subsequent loads: %s"), *BlueprintName.ToString(), *GraphName, *DiffDescription)); } } // At the close of this function, the FScopedBlueprintUnloader should prep // for following tests by unloading any blueprint dependencies that were // loaded for this one (should catch InitialBlueprint and ReloadedBlueprint) // // The FScopedBlueprintUnloader should also run garbage-collection after, // in hopes that the imports for this blueprint get destroyed so that they // don't invalidate other tests that share the same dependencies return !bDiffsFound; }