// 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); break; } } 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(TEXT("The property for the setter function for '%s' could not be generated."), *Function.PropertyName.ToString()); continue; } if (Function.SetterFunction) { if (!FunctionGraphHelper::GenerateViewModelFieldNotifySetter(*this, Function.SetterFunction, Function.Property, TEXT("NewValue"))) { MessageLog.Error(TEXT("The setter function for '%s' could not be generated."), *Function.PropertyName.ToString()); continue; } } if (Function.NetRepFunction) { if (!FunctionGraphHelper::GenerateViewModelFielNotifyBroadcast(*this, Function.NetRepFunction, Function.Property)) { MessageLog.Error(TEXT("The net rep function for '%s' could not be generated."), *Function.PropertyName.ToString()); continue; } } // HACK: Since the VariableSetter doesn't work because of the BlueprintReadOnly, clear it before processing the functions Function.SkelProperty->ClearPropertyFlags(CPF_BlueprintReadOnly); } Super::CreateFunctionList(); // HACK: Reset the BlueprintReadOnly flag for (const FGeneratedFunction& Function : GeneratedFunctions) { if (Function.SkelProperty == nullptr) { continue; } Function.SkelProperty->SetPropertyFlags(CPF_BlueprintReadOnly); } } 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_BlueprintReadOnly | CPF_DisableEditOnInstance); FGeneratedFunction* FoundGeneratedFunction = GeneratedFunctions.FindByPredicate([PropertyName](const FGeneratedFunction& Other) { return Other.PropertyName == PropertyName; }); if (FoundGeneratedFunction) { if (CompileOptions.CompileType == EKismetCompileType::SkeletonOnly) { FoundGeneratedFunction->SkelProperty = Property; ensureMsgf(false, TEXT("The generated function should already exist")); } else { FoundGeneratedFunction->Property = Property; if (FoundGeneratedFunction->NetRepFunction) { Property->SetPropertyFlags(CPF_Net); Property->RepNotifyFunc = FoundGeneratedFunction->NetRepFunction->GetFName(); } } } else if (CompileOptions.CompileType == EKismetCompileType::SkeletonOnly) { FGeneratedFunction GeneratedFunction; GeneratedFunction.PropertyName = PropertyName; GeneratedFunction.SkelProperty = Property; if (bGenerateSetterFunction) { const FString SetterName = FString::Printf(TEXT("Set%s"), *Property->GetName()); 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; } FunctionGraphHelper::AddFunctionArgument(GeneratedFunction.SetterFunction, Property, TEXT("NewValue")); } if (bGenerateReplicatedFunction) { const FString NetRepName = FString::Printf(TEXT("__RepNotify_%s"), *Property->GetName()); GeneratedFunction.NetRepFunction = FunctionGraphHelper::CreateIntermediateFunctionGraph( *this , *NetRepName , (FUNC_Private) , Property->GetMetaData(NAME_Category) , false); if (GeneratedFunction.NetRepFunction == nullptr) { MessageLog.Error(*FString::Printf(TEXT("The net replication function name '%s' already exists and could not be autogenerated."), *NetRepName)); continue; } else { 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::PrecompileFunction(FKismetFunctionContext& Context, EInternalCompilerFlags InternalFlags) { // HACK: Since the VariableSet doesn't work because of the BlueprintReadOnly, clear it before processing the functions for (const FGeneratedFunction& Function : GeneratedFunctions) { check(Function.Property); Function.Property->ClearPropertyFlags(CPF_BlueprintReadOnly); } Super::PrecompileFunction(Context, InternalFlags); } 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()); } // HACK: Reset the BlueprintReadOnly flag for (const FGeneratedFunction& Function : GeneratedFunctions) { check(Function.Property); Function.Property->SetPropertyFlags(CPF_BlueprintReadOnly); } } void FViewModelBlueprintCompilerContext::FinishCompilingClass(UClass* Class) { Super::FinishCompilingClass(Class); const bool bIsSkeletonOnly = CompileOptions.CompileType == EKismetCompileType::SkeletonOnly; if (!bIsSkeletonOnly) { if (UMVVMViewModelBlueprintGeneratedClass* BPGClass = CastChecked(Class)) { if (UMVVMViewModelBase* ViewModelBase = Cast(BPGClass->GetDefaultObject())) { BPGClass->InitializeFieldNotification(ViewModelBase); } } } } } //namespace #undef LOCTEXT_NAMESPACE