// Copyright Epic Games, Inc. All Rights Reserved. #include "UserDefinedStructureCompilerUtils.h" #include "Algo/Copy.h" #include "Containers/Array.h" #include "Containers/EnumAsByte.h" #include "Containers/Map.h" #include "Containers/Set.h" #include "Containers/SparseArray.h" #include "Containers/UnrealString.h" #include "CoreGlobals.h" #include "EdGraph/EdGraphPin.h" #include "EdGraphSchema_K2.h" #include "EdMode.h" #include "Engine/Blueprint.h" #include "Engine/BlueprintGeneratedClass.h" #include "StructUtils/UserDefinedStruct.h" #include "GameFramework/Actor.h" #include "HAL/PlatformCrt.h" #include "HAL/PlatformMath.h" #include "Internationalization/Internationalization.h" #include "Internationalization/Text.h" #include "K2Node.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/CompilerResultsLog.h" #include "Kismet2/StructureEditorUtils.h" #include "KismetCompiler.h" #include "KismetCompilerMisc.h" #include "Logging/LogCategory.h" #include "Logging/LogMacros.h" #include "Misc/AssertionMacros.h" #include "Misc/Guid.h" #include "Templates/Casts.h" #include "Templates/SubclassOf.h" #include "Templates/UnrealTemplate.h" #include "Tools/LegacyEdModeWidgetHelpers.h" #include "Trace/Detail/Channel.h" #include "UObject/Class.h" #include "UObject/FieldIterator.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/ObjectMacros.h" #include "UObject/Package.h" #include "UObject/SoftObjectPtr.h" #include "UObject/UObjectGlobals.h" #include "UObject/UObjectIterator.h" #include "UObject/UnrealNames.h" #include "UObject/UnrealType.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" #include "UserDefinedStructure/UserDefinedStructEditorData.h" #define LOCTEXT_NAMESPACE "StructureCompiler" struct FUserDefinedStructureCompilerInner { struct FBlueprintUserStructData { TArray SkeletonCDOData; TArray GeneratedCDOData; }; static void ClearStructReferencesInBP(UBlueprint* FoundBlueprint, TMap& BlueprintsToRecompile) { if (!BlueprintsToRecompile.Contains(FoundBlueprint)) { FBlueprintUserStructData& BlueprintData = BlueprintsToRecompile.Add(FoundBlueprint); // Write CDO data to temp archive //FObjectWriter SkeletonMemoryWriter(FoundBlueprint->SkeletonGeneratedClass->GetDefaultObject(), BlueprintData.SkeletonCDOData); //FObjectWriter MemoryWriter(FoundBlueprint->GeneratedClass->GetDefaultObject(), BlueprintData.GeneratedCDOData); for (UFunction* Function : TFieldRange(FoundBlueprint->GeneratedClass, EFieldIteratorFlags::ExcludeSuper)) { Function->Script.Empty(); } FoundBlueprint->Status = BS_Dirty; } } static void ReplaceStructWithTempDuplicate( UUserDefinedStruct* StructureToReinstance, TMap& BlueprintsToRecompile, TArray& ChangedStructs) { if (StructureToReinstance) { UUserDefinedStruct* DuplicatedStruct = NULL; { const FString ReinstancedName = FString::Printf(TEXT("STRUCT_REINST_%s"), *StructureToReinstance->GetName()); const FName UniqueName = MakeUniqueObjectName(GetTransientPackage(), UUserDefinedStruct::StaticClass(), FName(*ReinstancedName)); TGuardValue IsDuplicatingClassForReinstancing(GIsDuplicatingClassForReinstancing, true); DuplicatedStruct = (UUserDefinedStruct*)StaticDuplicateObject(StructureToReinstance, GetTransientPackage(), UniqueName, ~RF_Transactional); } DuplicatedStruct->Guid = StructureToReinstance->Guid; DuplicatedStruct->Bind(); DuplicatedStruct->StaticLink(true); DuplicatedStruct->PrimaryStruct = StructureToReinstance; DuplicatedStruct->Status = EUserDefinedStructureStatus::UDSS_Duplicate; DuplicatedStruct->SetFlags(RF_Transient); DuplicatedStruct->AddToRoot(); CastChecked(DuplicatedStruct->EditorData)->RecreateDefaultInstance(); // List of unique classes and structs to regenerate bytecode and property referenced objects list TSet StructsToRegenerateReferencesFor; for (TAllFieldsIterator FieldIt(RF_NoFlags, EInternalObjectFlags::Garbage); FieldIt; ++FieldIt) { FStructProperty* StructProperty = *FieldIt; if (StructProperty && (StructureToReinstance == StructProperty->Struct)) { if (UBlueprintGeneratedClass* OwnerClass = Cast(StructProperty->GetOwnerClass())) { if (UBlueprint* FoundBlueprint = Cast(OwnerClass->ClassGeneratedBy)) { ClearStructReferencesInBP(FoundBlueprint, BlueprintsToRecompile); StructProperty->Struct = DuplicatedStruct; StructsToRegenerateReferencesFor.Add(OwnerClass); } } else if (UUserDefinedStruct* OwnerStruct = Cast(StructProperty->GetOwnerStruct())) { check(OwnerStruct != DuplicatedStruct); const bool bValidStruct = (OwnerStruct->GetOutermost() != GetTransientPackage()) && IsValid(OwnerStruct) && (EUserDefinedStructureStatus::UDSS_Duplicate != OwnerStruct->Status.GetValue()); if (bValidStruct) { ChangedStructs.AddUnique(OwnerStruct); if (FStructureEditorUtils::FStructEditorManager::ActiveChange != FStructureEditorUtils::EStructureEditorChangeInfo::DefaultValueChanged) { // Don't change this for a default value only change, it won't get correctly replaced later StructProperty->Struct = DuplicatedStruct; StructsToRegenerateReferencesFor.Add(OwnerStruct); } } } else { UE_LOG(LogK2Compiler, Error, TEXT("ReplaceStructWithTempDuplicate unknown owner")); } } } // Make sure we update the list of objects referenced by structs after we replaced the struct in FStructProperties for (UStruct* Struct : StructsToRegenerateReferencesFor) { Struct->CollectBytecodeAndPropertyReferencedObjects(); } DuplicatedStruct->RemoveFromRoot(); for (UBlueprint* Blueprint : TObjectRange(RF_ClassDefaultObject, /** bIncludeDerivedClasses */ true, /** InternalExcludeFlags */ EInternalObjectFlags::Garbage)) { if (Blueprint && !BlueprintsToRecompile.Contains(Blueprint)) { FBlueprintEditorUtils::EnsureCachedDependenciesUpToDate(Blueprint); if (Blueprint->CachedUDSDependencies.Contains(StructureToReinstance)) { ClearStructReferencesInBP(Blueprint, BlueprintsToRecompile); } } } } } static void CleanAndSanitizeStruct(UUserDefinedStruct* StructToClean) { check(StructToClean); if (UUserDefinedStructEditorData* EditorData = Cast(StructToClean->EditorData)) { EditorData->CleanDefaultInstance(); } if (FStructureEditorUtils::FStructEditorManager::ActiveChange != FStructureEditorUtils::EStructureEditorChangeInfo::DefaultValueChanged) { StructToClean->SetSuperStruct(nullptr); StructToClean->Children = nullptr; StructToClean->DestroyChildPropertiesAndResetPropertyLinks(); StructToClean->Script.Empty(); StructToClean->MinAlignment = 0; StructToClean->ScriptAndPropertyObjectReferences.Empty(); StructToClean->ErrorMessage.Empty(); StructToClean->SetStructTrashed(true); } } static void LogError(UUserDefinedStruct* Struct, FCompilerResultsLog& MessageLog, const FString& ErrorMsg) { MessageLog.Error(*ErrorMsg); if (Struct && Struct->ErrorMessage.IsEmpty()) { Struct->ErrorMessage = ErrorMsg; } } static void CreateVariables(UUserDefinedStruct* Struct, const class UEdGraphSchema_K2* Schema, FCompilerResultsLog& MessageLog) { check(Struct && Schema); //FKismetCompilerUtilities::LinkAddedProperty push property to begin, so we revert the order for (int32 VarDescIdx = FStructureEditorUtils::GetVarDesc(Struct).Num() - 1; VarDescIdx >= 0; --VarDescIdx) { FStructVariableDescription& VarDesc = FStructureEditorUtils::GetVarDesc(Struct)[VarDescIdx]; VarDesc.bInvalidMember = true; FEdGraphPinType VarType = VarDesc.ToPinType(); FString ErrorMsg; if(!FStructureEditorUtils::CanHaveAMemberVariableOfType(Struct, VarType, &ErrorMsg)) { LogError( Struct, MessageLog, FText::Format( LOCTEXT("StructureGeneric_ErrorFmt", "Structure: {0} Error: {1}"), FText::FromString(Struct->GetFullName()), FText::FromString(ErrorMsg) ).ToString() ); continue; } FProperty* VarProperty = nullptr; bool bIsNewVariable = false; if (FStructureEditorUtils::FStructEditorManager::ActiveChange == FStructureEditorUtils::EStructureEditorChangeInfo::DefaultValueChanged) { VarProperty = FindFProperty(Struct, VarDesc.VarName); if (!ensureMsgf(VarProperty, TEXT("Could not find the expected property (%s); was the struct (%s) unexpectedly sanitized?"), *VarDesc.VarName.ToString(), *Struct->GetName())) { VarProperty = FKismetCompilerUtilities::CreatePropertyOnScope(Struct, VarDesc.VarName, VarType, NULL, CPF_None, Schema, MessageLog); bIsNewVariable = true; } } else { VarProperty = FKismetCompilerUtilities::CreatePropertyOnScope(Struct, VarDesc.VarName, VarType, NULL, CPF_None, Schema, MessageLog); bIsNewVariable = true; } if (VarProperty == nullptr) { LogError( Struct, MessageLog, FText::Format( LOCTEXT("VariableInvalidType_ErrorFmt", "The variable {0} declared in {1} has an invalid type {2}"), FText::FromName(VarDesc.VarName), FText::FromString(Struct->GetName()), UEdGraphSchema_K2::TypeToText(VarType) ).ToString() ); continue; } else if (bIsNewVariable) { VarProperty->SetFlags(RF_LoadCompleted); FKismetCompilerUtilities::LinkAddedProperty(Struct, VarProperty); } VarProperty->SetPropertyFlags(CPF_Edit | CPF_BlueprintVisible); if (VarDesc.bDontEditOnInstance) { VarProperty->SetPropertyFlags(CPF_DisableEditOnInstance); } if (VarDesc.bEnableSaveGame) { VarProperty->SetPropertyFlags(CPF_SaveGame); } if (VarDesc.bEnableMultiLineText) { VarProperty->SetMetaData("MultiLine", TEXT("true")); } if (VarDesc.bEnable3dWidget) { VarProperty->SetMetaData(FEdMode::MD_MakeEditWidget, TEXT("true")); } VarProperty->SetMetaData(TEXT("DisplayName"), *VarDesc.FriendlyName); VarProperty->SetMetaData(FBlueprintMetadata::MD_Tooltip, *VarDesc.ToolTip); VarProperty->AppendMetaData(VarDesc.MetaData); VarProperty->RepNotifyFunc = NAME_None; if (!VarDesc.DefaultValue.IsEmpty()) { VarProperty->SetMetaData(TEXT("MakeStructureDefaultValue"), *VarDesc.DefaultValue); } VarDesc.CurrentDefaultValue = VarDesc.DefaultValue; VarDesc.bInvalidMember = false; if (VarProperty->HasAnyPropertyFlags(CPF_InstancedReference | CPF_ContainsInstancedReference)) { Struct->StructFlags = EStructFlags(Struct->StructFlags | STRUCT_HasInstancedReference); } if (VarType.PinSubCategoryObject.IsValid()) { const UClass* ClassObject = Cast(VarType.PinSubCategoryObject.Get()); if (ClassObject && ClassObject->IsChildOf(AActor::StaticClass()) && (VarType.PinCategory == UEdGraphSchema_K2::PC_Object || VarType.PinCategory == UEdGraphSchema_K2::PC_Interface)) { // NOTE: Right now the code that stops hard AActor references from being set in unsafe places is tied to this flag, // which is not generally respected in other places for struct properties VarProperty->PropertyFlags |= CPF_DisableEditOnTemplate; } else { // clear the disable-default-value flag that might have been present (if this was an AActor variable before) VarProperty->PropertyFlags &= ~(CPF_DisableEditOnTemplate); } } } } static void InnerCompileStruct(UUserDefinedStruct* Struct, const class UEdGraphSchema_K2* K2Schema, class FCompilerResultsLog& MessageLog) { check(Struct); const int32 ErrorNum = MessageLog.NumErrors; Struct->SetMetaData(FBlueprintMetadata::MD_Tooltip, *FStructureEditorUtils::GetTooltip(Struct)); UUserDefinedStructEditorData* EditorData = CastChecked(Struct->EditorData); CreateVariables(Struct, K2Schema, MessageLog); Struct->Bind(); Struct->StaticLink(true); if (Struct->GetStructureSize() <= 0) { LogError( Struct, MessageLog, FText::Format( LOCTEXT("StructurEmpty_ErrorFmt", "Structure '{0}' is empty "), FText::FromString(Struct->GetFullName()) ).ToString() ); } FString DefaultInstanceError; EditorData->RecreateDefaultInstance(&DefaultInstanceError); if (!DefaultInstanceError.IsEmpty()) { LogError(Struct, MessageLog, DefaultInstanceError); } const bool bNoErrorsDuringCompilation = (ErrorNum == MessageLog.NumErrors); Struct->Status = bNoErrorsDuringCompilation ? EUserDefinedStructureStatus::UDSS_UpToDate : EUserDefinedStructureStatus::UDSS_Error; } static bool ShouldBeCompiled(const UUserDefinedStruct* Struct) { if (Struct && (EUserDefinedStructureStatus::UDSS_UpToDate == Struct->Status)) { return false; } return true; } static void BuildDependencyMapAndCompile(const TArray& ChangedStructs, FCompilerResultsLog& MessageLog) { struct FDependencyMapEntry { UUserDefinedStruct* Struct; TSet StructuresToWaitFor; FDependencyMapEntry() : Struct(NULL) {} void Initialize(UUserDefinedStruct* ChangedStruct, const TArray& AllChangedStructs) { Struct = ChangedStruct; check(Struct); for (FStructVariableDescription& VarDesc : FStructureEditorUtils::GetVarDesc(Struct)) { UUserDefinedStruct* StructType = Cast(VarDesc.SubCategoryObject.Get()); if (StructType && (VarDesc.Category == UEdGraphSchema_K2::PC_Struct) && AllChangedStructs.Contains(StructType)) { StructuresToWaitFor.Add(StructType); } } } }; TArray DependencyMap; for (UUserDefinedStruct* ChangedStruct : ChangedStructs) { DependencyMap.Add(FDependencyMapEntry()); DependencyMap.Last().Initialize(ChangedStruct, ChangedStructs); } while (DependencyMap.Num()) { int32 StructureToCompileIndex = INDEX_NONE; for (int32 EntryIndex = 0; EntryIndex < DependencyMap.Num(); ++EntryIndex) { if(0 == DependencyMap[EntryIndex].StructuresToWaitFor.Num()) { StructureToCompileIndex = EntryIndex; break; } } check(INDEX_NONE != StructureToCompileIndex); UUserDefinedStruct* Struct = DependencyMap[StructureToCompileIndex].Struct; check(Struct); FUserDefinedStructureCompilerInner::CleanAndSanitizeStruct(Struct); FUserDefinedStructureCompilerInner::InnerCompileStruct(Struct, GetDefault(), MessageLog); if (UUserDefinedStructEditorData* EditorData = Cast(Struct->EditorData)) { // Ensure that editor data is in sync w/ the current default instance (if valid) so that it can be reinitialized later. EditorData->RefreshValuesFromDefaultInstance(); } DependencyMap.RemoveAtSwap(StructureToCompileIndex); for (FDependencyMapEntry& MapEntry : DependencyMap) { MapEntry.StructuresToWaitFor.Remove(Struct); } } } }; void FUserDefinedStructureCompilerUtils::CompileStruct(class UUserDefinedStruct* Struct, class FCompilerResultsLog& MessageLog, bool bForceRecompile) { if (FStructureEditorUtils::UserDefinedStructEnabled() && Struct) { TArray ChangedStructs; if (FUserDefinedStructureCompilerInner::ShouldBeCompiled(Struct) || bForceRecompile) { ChangedStructs.Add(Struct); } TMap BlueprintsToRecompile; for (int32 StructIdx = 0; StructIdx < ChangedStructs.Num(); ++StructIdx) { UUserDefinedStruct* ChangedStruct = ChangedStructs[StructIdx]; if (ChangedStruct) { FStructureEditorUtils::BroadcastPreChange(ChangedStruct); FUserDefinedStructureCompilerInner::ReplaceStructWithTempDuplicate(ChangedStruct, BlueprintsToRecompile, ChangedStructs); ChangedStruct->Status = EUserDefinedStructureStatus::UDSS_Dirty; } } // COMPILE IN PROPER ORDER FUserDefinedStructureCompilerInner::BuildDependencyMapAndCompile(ChangedStructs, MessageLog); // UPDATE ALL THINGS DEPENDENT ON COMPILED STRUCTURES TSet ChangedStructsSet; ChangedStructsSet.Reserve(ChangedStructs.Num()); Algo::Copy(ChangedStructs, ChangedStructsSet); TSet BlueprintsThatHaveBeenRecompiled; FBlueprintEditorUtils::FindScriptStructsInNodes(ChangedStructsSet, [&BlueprintsThatHaveBeenRecompiled, &BlueprintsToRecompile](UBlueprint* Blueprint, UK2Node* Node) { // We need to recombine any nested subpins on this node, otherwise there will be an // unexpected amount of pins during reconstruction. FBlueprintEditorUtils::RecombineNestedSubPins(Node); if (Blueprint) { // The blueprint skeleton needs to be updated before we reconstruct the node // or else we may have member references that point to the old skeleton if (!BlueprintsThatHaveBeenRecompiled.Contains(Blueprint)) { BlueprintsThatHaveBeenRecompiled.Add(Blueprint); BlueprintsToRecompile.Remove(Blueprint); // Reapply CDO data FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); } Node->ReconstructNode(); } } ); for (TPair& Pair : BlueprintsToRecompile) { FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Pair.Key); } for (UUserDefinedStruct* ChangedStruct : ChangedStructs) { if (ChangedStruct) { FStructureEditorUtils::BroadcastPostChange(ChangedStruct); ChangedStruct->MarkPackageDirty(); } } } } void FUserDefinedStructureCompilerUtils::ReplaceStructWithTempDuplicateByPredicate( UUserDefinedStruct* StructureToReinstance, TFunctionRef ShouldReplaceStructInStructProperty, TFunctionRef PostReplace) { if (StructureToReinstance) { UUserDefinedStruct* DuplicatedStruct = NULL; { const FString ReinstancedName = FString::Printf(TEXT("STRUCT_REINST_%s"), *StructureToReinstance->GetName()); const FName UniqueName = MakeUniqueObjectName(GetTransientPackage(), UUserDefinedStruct::StaticClass(), FName(*ReinstancedName)); TGuardValue IsDuplicatingClassForReinstancing(GIsDuplicatingClassForReinstancing, true); DuplicatedStruct = (UUserDefinedStruct*)StaticDuplicateObject(StructureToReinstance, GetTransientPackage(), UniqueName, ~RF_Transactional); } DuplicatedStruct->Guid = StructureToReinstance->Guid; DuplicatedStruct->Bind(); DuplicatedStruct->StaticLink(true); DuplicatedStruct->PrimaryStruct = StructureToReinstance; DuplicatedStruct->Status = EUserDefinedStructureStatus::UDSS_Duplicate; DuplicatedStruct->SetFlags(RF_Transient); DuplicatedStruct->AddToRoot(); CastChecked(DuplicatedStruct->EditorData)->RecreateDefaultInstance(); // List of unique classes and structs to regenerate TSet StructsToRegenerateReferencesFor; for (TAllFieldsIterator FieldIt(RF_NoFlags, EInternalObjectFlags::Garbage); FieldIt; ++FieldIt) { FStructProperty* StructProperty = *FieldIt; if (StructProperty && (StructureToReinstance == StructProperty->Struct)) { if(ShouldReplaceStructInStructProperty(StructProperty)) { StructProperty->Struct = DuplicatedStruct; StructsToRegenerateReferencesFor.Add(StructProperty->GetOwnerClass()); } } } for (UStruct* Struct : StructsToRegenerateReferencesFor) { Struct->CollectBytecodeAndPropertyReferencedObjects(); PostReplace(Struct); } // as property owners are re-created, the duplicated struct will be GCed DuplicatedStruct->RemoveFromRoot(); } } #undef LOCTEXT_NAMESPACE