Files
UnrealEngineUWP/Engine/Plugins/Runtime/ModelViewViewModel/Source/ModelViewViewModelBlueprint/Private/ViewModel/MVVMViewModelBlueprintCompiler.cpp
patrick boutot f381f16a34 MVVM: First version of the viewmodel editor
#rb sebastien.nordgren, daren.cheng
#preflight 6256afff2b4502493e72edc2

#ROBOMERGE-AUTHOR: patrick.boutot
#ROBOMERGE-SOURCE: CL 19742639 via CL 19743098 via CL 19743424
#ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v938-19570697)

[CL 19745035 by patrick boutot in ue5-main branch]
2022-04-13 16:06:35 -04:00

328 lines
11 KiB
C++

// 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<bool> CVarAutogenerateSetter(
TEXT("MVVM.Viewmodel.GenerateSetter"),
true,
TEXT("If true, the setter function will be always be autogenerated"),
ECVF_Default);
static TAutoConsoleVariable<bool> 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<UMVVMViewModelBlueprint>(Blueprint) != nullptr;
}
void FViewModelBlueprintCompiler::Compile(UBlueprint* Blueprint, const FKismetCompilerOptions& CompileOptions, FCompilerResultsLog& Results)
{
if (UMVVMViewModelBlueprint* WidgetBlueprint = CastChecked<UMVVMViewModelBlueprint>(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<UMVVMViewModelBlueprint>(Blueprint);
}
FProperty* FViewModelBlueprintCompilerContext::FindPropertyByNameOnNewClass(FName PropertyName) const
{
for (FField* CurrentField = NewClass->ChildProperties; CurrentField != nullptr; CurrentField = CurrentField->Next)
{
if (CurrentField->GetFName() == PropertyName)
{
return CastField<FProperty>(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<UMVVMViewModelBlueprintGeneratedClass>((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<FProperty> 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 || GeneratedFunction.SetterFunction->GetFName() != FName(*SetterName))
{
MessageLog.Error(*FString::Printf(TEXT("The setter name %s already exist and could not be autogenerated."), *SetterName));
}
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 || GeneratedFunction.NetRepFunction->GetFName() != FName(*NetRepName))
{
MessageLog.Error(*FString::Printf(TEXT("The rep name %s already exist 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<UMVVMViewModelBlueprintGeneratedClass>(Blueprint->GetOutermost(), *NewClassName);
if (NewViewModelBlueprintClass == nullptr)
{
NewViewModelBlueprintClass = NewObject<UMVVMViewModelBlueprintGeneratedClass>(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<UMVVMViewModelBlueprintGeneratedClass>(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<UMVVMViewModelBlueprintGeneratedClass>(Class))
{
if (UMVVMViewModelBase* ViewModelBase = Cast<UMVVMViewModelBase>(BPGClass->GetDefaultObject()))
{
BPGClass->InitializeFieldNotification(ViewModelBase);
}
}
}
}
} //namespace
#undef LOCTEXT_NAMESPACE