// Copyright Epic Games, Inc. All Rights Reserved. #include "ViewModel/MVVMViewModelBlueprintCompiler.h" #include "ViewModel/MVVMViewModelBlueprint.h" #include "ViewModel/MVVMViewModelBlueprintGeneratedClass.h" #include "MVVMViewModelBase.h" #include "FieldNotification/CustomizationHelper.h" #include "FieldNotification/FieldNotificationHelpers.h" #include "K2Node_FunctionEntry.h" #include "K2Node_VariableSet.h" #include "Kismet2/CompilerResultsLog.h" #include "Kismet2/KismetReinstanceUtilities.h" #include "MVVMFunctionGraphHelper.h" #define LOCTEXT_NAMESPACE "ViewModelBlueprintCompiler" namespace UE::MVVM { static TAutoConsoleVariable CVarAutogenerateSetter( TEXT("MVVM.Viewmodel.GenerateSetter"), true, TEXT("If true, the setter function will be always be autogenerated"), ECVF_Default); static TAutoConsoleVariable CVarAutogenerateOnRep( TEXT("MVVM.Viewmodel.GenerateOnRep"), true, TEXT("If true, the OnRep function will be always be autogenerated"), ECVF_Default); ////////////////////////////////////////////////////////////////////////// // FViewModelBlueprintCompiler bool FViewModelBlueprintCompiler::CanCompile(const UBlueprint* Blueprint) { return Cast(Blueprint) != nullptr; } void FViewModelBlueprintCompiler::Compile(UBlueprint* Blueprint, const FKismetCompilerOptions& CompileOptions, FCompilerResultsLog& Results) { if (UMVVMViewModelBlueprint* WidgetBlueprint = CastChecked(Blueprint)) { FViewModelBlueprintCompilerContext Compiler(WidgetBlueprint, Results, CompileOptions); Compiler.Compile(); check(Compiler.NewClass); } } bool FViewModelBlueprintCompiler::GetBlueprintTypesForClass(UClass* ParentClass, UClass*& OutBlueprintClass, UClass*& OutBlueprintGeneratedClass) const { if (ParentClass == UMVVMViewModelBase::StaticClass() || ParentClass->IsChildOf(UMVVMViewModelBase::StaticClass())) { OutBlueprintClass = UMVVMViewModelBlueprint::StaticClass(); OutBlueprintGeneratedClass = UMVVMViewModelBlueprintGeneratedClass::StaticClass(); return true; } return false; } ////////////////////////////////////////////////////////////////////////// // FViewModelBlueprintCompilerContext UMVVMViewModelBlueprint* FViewModelBlueprintCompilerContext::GetViewModelBlueprint() const { return Cast(Blueprint); } FProperty* FViewModelBlueprintCompilerContext::FindPropertyByNameOnNewClass(FName PropertyName) const { for (FField* CurrentField = NewClass->ChildProperties; CurrentField != nullptr; CurrentField = CurrentField->Next) { if (CurrentField->GetFName() == PropertyName) { return CastField(CurrentField); } } return nullptr; } void FViewModelBlueprintCompilerContext::SaveSubObjectsFromCleanAndSanitizeClass(FSubobjectCollection& SubObjectsToSave, UBlueprintGeneratedClass* ClassToClean) { Super::SaveSubObjectsFromCleanAndSanitizeClass(SubObjectsToSave, ClassToClean); // Make sure our typed pointer is set check(ClassToClean == NewClass); NewViewModelBlueprintClass = CastChecked((UObject*)NewClass); } void FViewModelBlueprintCompilerContext::CleanAndSanitizeClass(UBlueprintGeneratedClass* ClassToClean, UObject*& InOutOldCDO) { Super::CleanAndSanitizeClass(ClassToClean, InOutOldCDO); NewViewModelBlueprintClass->FieldNotifyNames.Empty(); } void FViewModelBlueprintCompilerContext::CreateFunctionList() { for (const FGeneratedFunction& Function : GeneratedFunctions) { if (Function.Property == nullptr && Function.SkelProperty == nullptr) { MessageLog.Error(*FString::Printf(TEXT("Internal error. The property '%s' is invalid. Setter and net rep was not generated."), *Function.PropertyName.ToString())); continue; } if (Function.SetterFunction) { if (!FunctionGraphHelper::GenerateViewModelFieldNotifySetter(*this, Function.SetterFunction, Function.SkelProperty, TEXT("NewValue"))) { MessageLog.Error(*FString::Printf(TEXT("The setter function for '%s' could not be generated."), *Function.PropertyName.ToString())); continue; } } if (Function.NetRepFunction) { if (!FunctionGraphHelper::GenerateViewModelFielNotifyBroadcast(*this, Function.NetRepFunction, Function.SkelProperty)) { MessageLog.Error(*FString::Printf(TEXT("The net rep function for '%s' could not be generated."), *Function.PropertyName.ToString())); continue; } } } Super::CreateFunctionList(); } void FViewModelBlueprintCompilerContext::CreateClassVariablesFromBlueprint() { Super::CreateClassVariablesFromBlueprint(); const FName NAME_MetaDataBlueprintSetter = "BlueprintSetter"; const FName NAME_Category = "Category"; for (TFieldIterator PropertyIt(NewClass, EFieldIterationFlags::None); PropertyIt; ++PropertyIt) { FProperty* Property = *PropertyIt; FName PropertyName = Property->GetFName(); bool bGenerateSetterFunction = CVarAutogenerateSetter.GetValueOnGameThread() || Property->HasMetaData(UE::FieldNotification::FCustomizationHelper::MetaData_FieldNotify); bool bGenerateReplicatedFunction = CVarAutogenerateOnRep.GetValueOnGameThread(); // Property->RepNotifyFunc.IsNone(); if (bGenerateSetterFunction || bGenerateReplicatedFunction) { Property->SetPropertyFlags(CPF_DisableEditOnInstance); const FString SetterName = FString::Printf(TEXT("Set%s"), *Property->GetName()); const FString NetRepName = FString::Printf(TEXT("__RepNotify_%s"), *Property->GetName()); #if WITH_EDITOR if (bGenerateSetterFunction) { Property->SetMetaData(FBlueprintMetadata::MD_PropertySetFunction, *SetterName); } #endif if (CompileOptions.CompileType == EKismetCompileType::SkeletonOnly) { FGeneratedFunction GeneratedFunction; GeneratedFunction.PropertyName = PropertyName; GeneratedFunction.SkelProperty = Property; if (GeneratedFunctions.ContainsByPredicate([PropertyName](const FGeneratedFunction& Other) { return Other.PropertyName == PropertyName; })) { MessageLog.Error(*FString::Printf(TEXT("Internal error. The functions are already generated for properpty."), *PropertyName.ToString())); continue; } if (bGenerateSetterFunction) { GeneratedFunction.SetterFunction = FunctionGraphHelper::CreateIntermediateFunctionGraph( *this , *SetterName , (FUNC_BlueprintCallable | FUNC_Public) , Property->GetMetaData(NAME_Category) , false); if (GeneratedFunction.SetterFunction == nullptr) { MessageLog.Error(*FString::Printf(TEXT("The setter name '%s' already exists and could not be autogenerated."), *SetterName)); continue; } if (GeneratedFunction.SetterFunction->GetFName() != *SetterName) { MessageLog.Error(*FString::Printf(TEXT("The setter name '%s' was generated with a different name."), *SetterName)); continue; } FunctionGraphHelper::AddFunctionArgument(GeneratedFunction.SetterFunction, Property, TEXT("NewValue")); } if (bGenerateReplicatedFunction) { GeneratedFunction.NetRepFunction = FunctionGraphHelper::CreateIntermediateFunctionGraph( *this , *NetRepName , (FUNC_Private) , Property->GetMetaData(NAME_Category) , false); if (GeneratedFunction.NetRepFunction == nullptr) { MessageLog.Error(*FString::Printf(TEXT("The netrep function name '%s' already exists and could not be autogenerated."), *NetRepName)); continue; } if (GeneratedFunction.NetRepFunction->GetFName() != *NetRepName) { MessageLog.Error(*FString::Printf(TEXT("The netrep function name '%s' was generated with a different name."), *NetRepName)); continue; } Property->SetPropertyFlags(CPF_Net); Property->RepNotifyFunc = GeneratedFunction.NetRepFunction->GetFName(); FunctionGraphHelper::AddFunctionArgument(GeneratedFunction.SetterFunction, Property, TEXT("NewValue")); } GeneratedFunctions.Add(GeneratedFunction); } NewViewModelBlueprintClass->FieldNotifyNames.Emplace(Property->GetFName()); } } } void FViewModelBlueprintCompilerContext::SpawnNewClass(const FString& NewClassName) { NewViewModelBlueprintClass = FindObject(Blueprint->GetOutermost(), *NewClassName); if (NewViewModelBlueprintClass == nullptr) { NewViewModelBlueprintClass = NewObject(Blueprint->GetOutermost(), FName(*NewClassName), RF_Public | RF_Transactional); } else { // Already existed, but wasn't linked in the Blueprint yet due to load ordering issues FBlueprintCompileReinstancer::Create(NewViewModelBlueprintClass); } NewClass = NewViewModelBlueprintClass; } void FViewModelBlueprintCompilerContext::OnNewClassSet(UBlueprintGeneratedClass* ClassToUse) { NewViewModelBlueprintClass = CastChecked(ClassToUse); } void FViewModelBlueprintCompilerContext::PostcompileFunction(FKismetFunctionContext& Context) { Super::PostcompileFunction(Context); if (Context.Function && Context.Function->HasMetaData(UE::FieldNotification::FCustomizationHelper::MetaData_FieldNotify)) { if (!UE::FieldNotification::Helpers::IsValidAsField(Context.Function)) { MessageLog.Error(*LOCTEXT("FieldNotify_IsEventGraph", "Function @@ cannot be a FieldNotify. A function needs to be const, returns a single properties, has no inputs, not be an event or a net function.").ToString(), Context.EntryPoint); } NewViewModelBlueprintClass->FieldNotifyNames.Emplace(Context.Function->GetFName()); } } void FViewModelBlueprintCompilerContext::FinishCompilingClass(UClass* Class) { Super::FinishCompilingClass(Class); auto DeleteGraph = [](UEdGraph* Graph) { if (Graph) { ensureMsgf(Graph->HasAnyFlags(RF_Transient), TEXT("The graph should be temporary and should be generated automatically.")); // GC may not have clean the graph (GC doesn't run when bRegenerateSkeletonOnly is on) Graph->Rename(nullptr, Graph->GetOuter(), REN_DoNotDirty | REN_ForceNoResetLoaders); } }; const bool bIsSkeletonOnly = CompileOptions.CompileType == EKismetCompileType::SkeletonOnly; if (!bIsSkeletonOnly) { if (UMVVMViewModelBlueprintGeneratedClass* BPGClass = CastChecked(Class)) { if (UMVVMViewModelBase* ViewModelBase = Cast(BPGClass->GetDefaultObject())) { BPGClass->InitializeFieldNotification(ViewModelBase); } } } for (FGeneratedFunction& GeneratedFunction : GeneratedFunctions) { DeleteGraph(GeneratedFunction.SetterFunction); DeleteGraph(GeneratedFunction.NetRepFunction); } } } //namespace #undef LOCTEXT_NAMESPACE