Files
UnrealEngineUWP/Engine/Source/Developer/BlueprintNativeCodeGen/Private/BlueprintNativeCodeGenModule.cpp
Mike Beach 5f72049a23 Copying //UE4/Dev-Blueprints to //UE4/Dev-Main (Source: //UE4/Dev-Blueprints @ 3080732)
#lockdown Nick.Penwarden
#rb none

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3058607 on 2016/07/20 by Mike.Beach

	Preventing a uneeded FStructOnScope allocation from happening - was causing issues with the memstomp allocator (internally, FStructOnScope was allocating mem of zero size and then asserting on the returned pointer).

Change 3059586 on 2016/07/21 by Maciej.Mroz

	Added comments

Change 3061614 on 2016/07/22 by Ben.Cosh

	Fix for a bug in the blueprint profiler tunnel mapping code that caused asserts when internal pure tunnel pins were linked to each other as pass thru.
	#Jira UE-33654 - Editor crash on compilation when feeding impure data to macros implemented via blueprint while profiling is enabled
	#Jira UE-33138 - BP Profiler: crash when trying to set child actor in profiler
	#Proj BlueprintProfiler

Change 3061686 on 2016/07/22 by Mike.Beach

	Keeping cyclically dependent Blueprints from infinitely trying to recompile each other, when both have an unrelated error that will not be resolved by compiling the other.

Change 3061760 on 2016/07/22 by Ben.Cosh

	Minor refactor of the delegate event code in the profiler to fix some stubborn issues.
	#Jira UE-33466 - Key events still have problems with recording event stats correctly
	#Proj BlueprintProfiler, Kismet

Change 3061819 on 2016/07/22 by Maciej.Mroz

	#jira UE-26676 Blueprint native events give error when output ref params aren't in a specific order

	Force a overriden function to have the same parameter's order as the original one.

Change 3061854 on 2016/07/22 by Bob.Tellez

	Duplicate CL#3058653 //Fortnite/Main

	#UE4 Now actually removing deprecated pins from non-blueprint graphs. Also MarkPendingKill now happens in UEdGraphNode's BeginDestroy instead of its destructor to ensure supporting code can safely access references to other UObjects.

Change 3062634 on 2016/07/23 by Mike.Beach

	Accounting for EditablePinBase nodes whose UserDefinedPins have the wrong direction assigned to them (we now validate the direction, and expect it to reflect the EdGraphPin's). We already had made this fixup in CustomEvent nodes, but others (like collapsed tunnels, and math expression nodes) needed the fixup as well.

Change 3062926 on 2016/07/25 by Ben.Cosh

	Added functionality to the blueprint compiler to detect local event function calls and handle them better in profiling conditions.
	#Jira UE-32869 - Nodes called after a custom event call do not record stats in the profiler
	#Proj CoreUObject, BlueprintProfiler, UnrealEd, KismetCompiler, BlueprintGraph

	- Added script emitted inline event start/stop calls for inline events so we can pull out and process these events discretely
	- Looked into adding something similar for all events but couldn't find a good place to put it/get it operational so it caught more standard events.

Change 3063406 on 2016/07/25 by Ben.Cosh

	Modifying the execution graph selection highlight coloring.
	#Jira none
	#Proj EditorStyle

Change 3063505 on 2016/07/25 by Ben.Cosh

	The blueprint profiler tunnel mapping was missing a call seek past reroute nodes
	#Jira UE-33670 - Reroute nodes used in 'for' loops break profiler communication
	#Proj BlueprintProfiler

Change 3063508 on 2016/07/25 by Ben.Cosh

	Fixed a minor bug in the stat creation code that reported tunnel pure timings twice.
	#Jira UE-33707 - BP Profiler - Pure nodes internal to macro reported twice in tree view
	#Proj Kismet

Change 3063511 on 2016/07/25 by Ben.Cosh

	Fix for a bug introduced that caused pie instances to mapped twice in the blueprint profiler.
	#Jira UE-33697 - BP Profiler: Extra instance showing up in the tree view
	#Proj BlueprintProfiler

Change 3063627 on 2016/07/25 by Maciej.Mroz

	#jira UE-33027 Crash when implementing interface to child blueprint and then implementing it with parent blueprint

	Removed premature validation.

Change 3064349 on 2016/07/26 by Maciej.Mroz

	#jira UE-32942 BP Nativization: Reduce the size of executable files

	Enabled and fixed local variables on event graph. Local variable can be only created as return value (so we're sure it doesn't require any resistency between calls.)
	It reduces size of executable file (2MB in Orion.exe dev config).
	It reduces number of member variables in nativized class (local varaibles in functions are not uproperties, so the number of generated of objects decreases).

Change 3064788 on 2016/07/26 by Ryan.Rauschkolb

	Fixed Splitting a Rotation input struct pin results in any previously entered values shifting to a different axis
	#UE-31931

Change 3064828 on 2016/07/26 by Ryan.Rauschkolb

	Removed flag to disable Single Layout Blueprint Editor (no longer experimental feature)
	#jira UE-32038

Change 3064966 on 2016/07/26 by Ryan.Rauschkolb

	Fixed Comment bubbles don't handle widget visibility correctly
	#UE-21278

Change 3068095 on 2016/07/28 by Maciej.Mroz

	#jira UE-32942 BP Nativization: Reduce the size of executable files

	Private and protected properties have PrivatePropertyOffset (PPO) function in .generated.h. This function allows the nativized code to access the property without using UProperty.
	-It reduces the size of executable file (added by nativized plugin) about 10%. The OrionGame.exe (development config) is 6MB smaller.
	-It reduces the number of FindField function calls and stativ variables in the nativized code.

	List of inaccessible properties (that cannot be accessed using PPO) is logged while cooking (with nativization enabled).

Change 3068122 on 2016/07/28 by Maciej.Mroz

	#jira UE-32942 BP Nativization: Reduce the size of executable files

	Hardcoded asset paths are split, so string literals can be better reused.
	Added UDynamicClass::FindStructPropertyChecked. It replaces FindFieldChecked<UStructProperty>, without inlining, and implicit FName constructor.

	It reduced the size of OrionGame.exe 1MB.

Change 3068159 on 2016/07/28 by Maciej.Mroz

	#jira UE-32806 GitHub 2569 : Exposed GetComponentByClass to blueprint
	#2569: Exposed GetComponentByClass to blueprint (Contributed by Koderz)

Change 3069715 on 2016/07/29 by Maciej.Mroz

	#jira UE-33460 [CrashReport] UE4Editor_CoreUObject!UObjectPropertyBase::ParseObjectPropertyValue() [propertybaseobject.cpp:237]

	UObjectPropertyBase::ParseObjectPropertyValue won;t crash when property is invalid.
	Property validation in UserDefinedStruct. THe struct is not recompiled on load, so it must be validated after serialization.

Change 3070569 on 2016/07/29 by Bob.Tellez

	Duplicating CL#3070518 from //Fortnite/Main

	#UE4 Deprecated pin removal logic is now exclusively in UEdGraphNode::PostLoad. DeprecatedPinWatches fixup is now done in K2Node::PostLoad.

Change 3071292 on 2016/07/30 by Mike.Beach

	Preventing the Blueprint reinstancer's Function/PropertyMap from being GC'd during compile. This was causing issues where new functions/properties were being allocated in the same pointer location, and UpdateBytecodeReferences() was replacing those references as well (specifically in unrelated class's Children->Next chain, linking in functions/properties that did not belong to that class). This was causing a multitude of problems (mainly bad property offset read/writes and endless field iterator loops).

	#jira UE-29631

Change 3072078 on 2016/08/01 by Maciej.Mroz

	#jira UE-33423, UE-33860

	Removed too strint ensures.
	Fixed FGraphObjectTextFactory - After Custom Event nodes are pased, Skel Class is recompiled, because other pasted nodes may require its signature.

Change 3072166 on 2016/08/01 by Dan.Oconnor

	PR #2589: fix EaseIn / EaseOut descriptions (Contributed by dsine-de)

	#jira UE-32997

Change 3072614 on 2016/08/01 by Mike.Beach

	Fixing an issue where hot-reloading a Blueprint parent class was not reinstancing skeleton CDOs. This caused problems later where the skel class layout didn't reflect the CDO object.

	#jira UE-29613
	#codreview Maciej.Mroz, Phillip.Kavan

Change 3073939 on 2016/08/02 by Dan.Oconnor

	Final fix for function graphs that cannot be deleted (bAllowDeletion erroneously set to false). Issue only manifests with assets created before 4.11, as the original bug was fixed in 2842578
	#jira UE-19062

Change 3075793 on 2016/08/03 by Maciej.Mroz

	#jira UE-30473 Moving child component in child blueprint forces parent to become dirty

	Don't make parent BP package dirty, when a component in child BP was modified.

Change 3076990 on 2016/08/04 by Ben.Cosh

	This fixes issues with mapping tunnel boundary pure nodes and addresses some asserts recently introduced.
	#Jira UE-33691 - Assert when compiling Blueprint with profiler instrumentation
	#Jira UE-33138 - BP Profiler: crash when trying to set child actor in profiler
	#Jira UE-33654 - Editor crash on compilation when feeding impure data to macros implemented via blueprint while profiling is enabled
	#Proj Kismet, BlueprintProfiler, BlueprintGraph

	- Fixed inline event detection ( it was causing function stats to fail, happened across it )
	- Updated pure node lookup to use the entry pin, this was required because pure nodes span function contexts and lookup is a problem in nested tunnels.
	- Updated tunnel pure node code, added a stubbed pure chain early on external pure links add this and it maps at an appropriate time.
	- Changed the way nested tunnels are mapped, now only top level tunnels are gathered mapping the blueprint and these map nested tunnels and register them.
	- Updated pure node stat refreshes and heat level updates ( this was causing a bunch of extra cost with my changes )
	- Fixed an issue with script perf data that caused nan's with no samples.
	- Updated pure node playback to cache pure nodes and avoid a second involved lookup when applying timings.
	- Renamed FScriptExecutionPureNode to FScriptExecutionPureChainNode to better reflect it's updated role.
	- Added extra editor stat collection for checking the cost breakdown of the profiler ( hottest path and heat level calcs now have discreet timings )

Change 3079235 on 2016/08/05 by Phillip.Kavan

	Fix for a bug in pi to pure node lookup functionality that caused pure nodes to be mapped more than once.
	#Jira UE-34254 - Crash compiling blueprint with instrumentation - !ScriptExecNode.IsValid()
	#Proj BlueprintProfiler, Kismet

	- Fixed the code to focus observed pins
	- Fixed event pin mapping code that was failing when linked directly to a tunnel node.

	Note: Submitting on behalf of BenC (per MikeB).

Change 3080417 on 2016/08/08 by Ben.Cosh

	This fixes the way execution path stats are calculated.
	#Jira UE-34150 - Exec pin containers in the profiler are accumulating time incorrectly.
	#Proj Kismet

Change 3080484 on 2016/08/08 by Maciej.Mroz

	#jira UE-28625 Direction of GetOverlapInfos parameter doesn't match

Change 3080571 on 2016/08/08 by Ben.Cosh

	This addresses some flaws in the fix submitted in CL 3080417 that were discovered after submission.
	#Jira UE-34150 - Exec pin containers in the profiler are accumulating time incorrectly.
	#Proj Kismet

[CL 3080751 by Mike Beach in Main branch]
2016-08-08 11:42:16 -04:00

744 lines
24 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "BlueprintNativeCodeGenPCH.h"
#include "AssetRegistryModule.h"
#include "BlueprintNativeCodeGenManifest.h"
#include "BlueprintNativeCodeGenModule.h"
#include "BlueprintNativeCodeGenUtils.h"
#include "IBlueprintCompilerCppBackendModule.h"
#include "BlueprintEditorUtils.h"
#include "Engine/Blueprint.h" // for EBlueprintType
#include "Engine/InheritableComponentHandler.h"
/*******************************************************************************
* NativizationCookControllerImpl
******************************************************************************/
namespace NativizationCookControllerImpl
{
// If you change this plugin name you must update logic in CookCommand.Automation.cs
static const TCHAR* DefaultPluginName = TEXT("NativizedAssets");
}
/*******************************************************************************
* FBlueprintNativeCodeGenModule
******************************************************************************/
class FBlueprintNativeCodeGenModule : public IBlueprintNativeCodeGenModule
, public IBlueprintNativeCodeGenCore
{
public:
FBlueprintNativeCodeGenModule()
{
}
//~ Begin IBlueprintNativeCodeGenModule interface
virtual void Convert(UPackage* Package, ESavePackageResult ReplacementType, const TCHAR* PlatformName) override;
virtual void SaveManifest(int32 Id = -1) override;
virtual void MergeManifest(int32 ManifestIdentifier) override;
virtual void FinalizeManifest() override;
virtual void GenerateStubs() override;
virtual void GenerateFullyConvertedClasses() override;
virtual void MarkUnconvertedBlueprintAsNecessary(TAssetPtr<UBlueprint> BPPtr) override;
virtual const TMultiMap<FName, TAssetSubclassOf<UObject>>& GetFunctionsBoundToADelegate() override;
protected:
virtual void Initialize(const FNativeCodeGenInitData& InitData) override;
virtual void InitializeForRerunDebugOnly(const TArray< TPair< FString, FString > >& CodegenTargets) override;
//~ End IBlueprintNativeCodeGenModule interface
//~ Begin FScriptCookReplacmentCoordinator interface
virtual EReplacementResult IsTargetedForReplacement(const UPackage* Package) const override;
virtual EReplacementResult IsTargetedForReplacement(const UObject* Object) const override;
virtual UClass* FindReplacedClassForObject(const UObject* Object) const override;
virtual UObject* FindReplacedNameAndOuter(UObject* Object, FName& OutName) const override;
//~ End FScriptCookReplacmentCoordinator interface
private:
void ReadConfig();
void FillTargetedForReplacementQuery();
void FillIsFunctionUsedInADelegate();
FBlueprintNativeCodeGenManifest& GetManifest(const TCHAR* PlatformName);
void GenerateSingleStub(UBlueprint* BP, const TCHAR* PlatformName);
void CollectBoundFunctions(UBlueprint* BP);
void GenerateSingleAsset(UField* ForConversion, const TCHAR* PlatformName, TSharedPtr<FNativizationSummary> NativizationSummary = TSharedPtr<FNativizationSummary>());
TMap< FString, TUniquePtr<FBlueprintNativeCodeGenManifest> > Manifests;
TArray<FString> ExcludedAssetTypes;
TArray<TAssetSubclassOf<UBlueprint>> ExcludedBlueprintTypes;
TSet<FStringAssetReference> ExcludedAssets;
TArray<FString> TargetPlatformNames;
// A stub-wrapper must be generated only if the BP is really accessed/required by some other generated code.
TSet<TAssetPtr<UBlueprint>> StubsRequiredByGeneratedCode;
TSet<TAssetPtr<UBlueprint>> AllPotentialStubs;
TSet<TAssetPtr<UBlueprint>> ToGenerate;
TMultiMap<FName, TAssetSubclassOf<UObject>> FunctionsBoundToADelegate; // is a function could be bound to a delegate, then it must have UFUNCTION macro. So we cannot optimize it.
};
void FBlueprintNativeCodeGenModule::ReadConfig()
{
GConfig->GetArray(TEXT("BlueprintNativizationSettings"), TEXT("ExcludedAssetTypes"), ExcludedAssetTypes, GEditorIni);
{
TArray<FString> ExcludedBlueprintTypesPath;
GConfig->GetArray(TEXT("BlueprintNativizationSettings"), TEXT("ExcludedBlueprintTypes"), ExcludedBlueprintTypesPath, GEditorIni);
for (FString& Path : ExcludedBlueprintTypesPath)
{
TAssetSubclassOf<UBlueprint> ClassPtr;
ClassPtr = FStringAssetReference(Path);
ClassPtr.LoadSynchronous();
ExcludedBlueprintTypes.Add(ClassPtr);
}
}
TArray<FString> ExcludedAssetPaths;
GConfig->GetArray(TEXT("BlueprintNativizationSettings"), TEXT("ExcludedAssets"), ExcludedAssetPaths, GEditorIni);
for (FString& Path : ExcludedAssetPaths)
{
ExcludedAssets.Add(FStringAssetReference(Path));
}
}
void FBlueprintNativeCodeGenModule::MarkUnconvertedBlueprintAsNecessary(TAssetPtr<UBlueprint> BPPtr)
{
StubsRequiredByGeneratedCode.Add(BPPtr);
}
void FBlueprintNativeCodeGenModule::FillTargetedForReplacementQuery()
{
IBlueprintCompilerCppBackendModule& BackEndModule = (IBlueprintCompilerCppBackendModule&)IBlueprintCompilerCppBackendModule::Get();
auto& ConversionQueryDelegate = BackEndModule.OnIsTargetedForConversionQuery();
auto ShouldConvert = [](const UObject* AssetObj)
{
if (ensure(IBlueprintNativeCodeGenCore::Get()))
{
EReplacementResult ReplacmentResult = IBlueprintNativeCodeGenCore::Get()->IsTargetedForReplacement(AssetObj);
return ReplacmentResult == EReplacementResult::ReplaceCompletely;
}
return false;
};
ConversionQueryDelegate.BindStatic(ShouldConvert);
auto LocalMarkUnconvertedBlueprintAsNecessary = [](TAssetPtr<UBlueprint> BPPtr)
{
IBlueprintNativeCodeGenModule::Get().MarkUnconvertedBlueprintAsNecessary(BPPtr);
};
BackEndModule.OnIncludingUnconvertedBP().BindStatic(LocalMarkUnconvertedBlueprintAsNecessary);
}
namespace
{
void GetFieldFormPackage(const UPackage* Package, UStruct*& OutStruct, UEnum*& OutEnum, EObjectFlags ExcludedFlags = RF_Transient)
{
TArray<UObject*> Objects;
GetObjectsWithOuter(Package, Objects, false);
for (UObject* Entry : Objects)
{
if (Entry->HasAnyFlags(ExcludedFlags))
{
continue;
}
if (FBlueprintSupport::IsDeferredDependencyPlaceholder(Entry))
{
continue;
}
// Not a skeleton class
if (UClass* AsClass = Cast<UClass>(Entry))
{
if (UBlueprint* GeneratingBP = Cast<UBlueprint>(AsClass->ClassGeneratedBy))
{
if (AsClass != GeneratingBP->GeneratedClass)
{
continue;
}
}
}
OutStruct = Cast<UStruct>(Entry);
if (OutStruct)
{
break;
}
OutEnum = Cast<UEnum>(Entry);
if (OutEnum)
{
break;
}
}
}
}
void FBlueprintNativeCodeGenModule::CollectBoundFunctions(UBlueprint* BP)
{
TArray<UFunction*> Functions = IBlueprintCompilerCppBackendModule::CollectBoundFunctions(BP);
for (UFunction* Func : Functions)
{
if (Func)
{
FunctionsBoundToADelegate.AddUnique(Func->GetFName(), Func->GetOwnerClass());
}
}
}
const TMultiMap<FName, TAssetSubclassOf<UObject>>& FBlueprintNativeCodeGenModule::GetFunctionsBoundToADelegate()
{
return FunctionsBoundToADelegate;
}
void FBlueprintNativeCodeGenModule::FillIsFunctionUsedInADelegate()
{
IBlueprintCompilerCppBackendModule& BackEndModule = (IBlueprintCompilerCppBackendModule&)IBlueprintCompilerCppBackendModule::Get();
auto IsFunctionUsed = [](const UFunction* InFunction) -> bool
{
auto& TargetFunctionsBoundToADelegate = IBlueprintNativeCodeGenModule::Get().GetFunctionsBoundToADelegate();
return InFunction && (nullptr != TargetFunctionsBoundToADelegate.FindPair(InFunction->GetFName(), InFunction->GetOwnerClass()));
};
BackEndModule.GetIsFunctionUsedInADelegateCallback().BindStatic(IsFunctionUsed);
}
void FBlueprintNativeCodeGenModule::Initialize(const FNativeCodeGenInitData& InitData)
{
ReadConfig();
IBlueprintNativeCodeGenCore::Register(this);
// Each platform will need a manifest, because each platform could cook different assets:
for (auto& Platform : InitData.CodegenTargets)
{
const TCHAR* TargetDirectory = *Platform.Value;
FString OutputPath = FPaths::Combine(TargetDirectory, NativizationCookControllerImpl::DefaultPluginName);
Manifests.Add(FString(*Platform.Key), TUniquePtr<FBlueprintNativeCodeGenManifest>(new FBlueprintNativeCodeGenManifest(NativizationCookControllerImpl::DefaultPluginName, OutputPath)));
TargetPlatformNames.Add(Platform.Key);
// Clear source code folder
const FString SourceCodeDir = GetManifest(*Platform.Key).GetTargetPaths().PluginSourceDir();
UE_LOG(LogBlueprintCodeGen, Log, TEXT("Clear nativized source code directory: %s"), *SourceCodeDir);
IFileManager::Get().DeleteDirectory(*SourceCodeDir, false, true);
}
FillTargetedForReplacementQuery();
FillIsFunctionUsedInADelegate();
}
void FBlueprintNativeCodeGenModule::InitializeForRerunDebugOnly(const TArray< TPair< FString, FString > >& CodegenTargets)
{
ReadConfig();
IBlueprintNativeCodeGenCore::Register(this);
FillTargetedForReplacementQuery();
FillIsFunctionUsedInADelegate();
for (const auto& Platform : CodegenTargets)
{
// load the old manifest:
const TCHAR* TargetDirectory = *Platform.Value;
FString OutputPath = FPaths::Combine(TargetDirectory, NativizationCookControllerImpl::DefaultPluginName, *FBlueprintNativeCodeGenPaths::GetDefaultManifestPath());
Manifests.Add(FString(*Platform.Key), TUniquePtr<FBlueprintNativeCodeGenManifest>(new FBlueprintNativeCodeGenManifest(FPaths::ConvertRelativePathToFull(OutputPath))));
//FBlueprintNativeCodeGenManifest OldManifest(FPaths::ConvertRelativePathToFull(OutputPath));
// reconvert every assets listed in the manifest:
for (const auto& ConversionTarget : GetManifest(*Platform.Key).GetConversionRecord())
{
// load the package:
UPackage* Package = LoadPackage(nullptr, *ConversionTarget.Value.TargetObjPath, LOAD_None);
if (!Package)
{
UE_LOG(LogBlueprintCodeGen, Error, TEXT("Unable to load the package: %s"), *ConversionTarget.Value.TargetObjPath);
continue;
}
// reconvert it
Convert(Package, ESavePackageResult::ReplaceCompletely, *Platform.Key);
}
// reconvert every unconverted dependency listed in the manifest:
for (const auto& ConversionTarget : GetManifest(*Platform.Key).GetUnconvertedDependencies())
{
// load the package:
UPackage* Package = LoadPackage(nullptr, *ConversionTarget.Key.GetPlainNameString(), LOAD_None);
UStruct* Struct = nullptr;
UEnum* Enum = nullptr;
GetFieldFormPackage(Package, Struct, Enum);
UBlueprint* BP = Cast<UBlueprint>(CastChecked<UClass>(Struct)->ClassGeneratedBy);
if (ensure(BP))
{
CollectBoundFunctions(BP);
GenerateSingleStub(BP, *Platform.Key);
}
}
for (TAssetPtr<UBlueprint>& BPPtr : ToGenerate)
{
UBlueprint* BP = BPPtr.LoadSynchronous();
if (ensure(BP))
{
GenerateSingleAsset(BP->GeneratedClass, *Platform.Key);
}
}
}
}
void FBlueprintNativeCodeGenModule::GenerateFullyConvertedClasses()
{
TSharedPtr<FNativizationSummary> NativizationSummary(new FNativizationSummary());
for (TAssetPtr<UBlueprint>& BPPtr : ToGenerate)
{
UBlueprint* BP = BPPtr.LoadSynchronous();
if (ensure(BP))
{
for (const FString& PlatformName : TargetPlatformNames)
{
GenerateSingleAsset(BP->GeneratedClass, *PlatformName, NativizationSummary);
}
}
}
if (NativizationSummary->InaccessiblePropertyStat.Num())
{
UE_LOG(LogBlueprintCodeGen, Warning, TEXT("Nativization Summary - Inaccessible Properties:"));
NativizationSummary->InaccessiblePropertyStat.ValueSort(TGreater<int32>());
for (auto& Iter : NativizationSummary->InaccessiblePropertyStat)
{
UE_LOG(LogBlueprintCodeGen, Warning, TEXT("\t %s \t - %d"), *Iter.Key.ToString(), Iter.Value);
}
}
}
FBlueprintNativeCodeGenManifest& FBlueprintNativeCodeGenModule::GetManifest(const TCHAR* PlatformName)
{
FString PlatformNameStr(PlatformName);
TUniquePtr<FBlueprintNativeCodeGenManifest>* Result = Manifests.Find(PlatformNameStr);
check(Result->IsValid());
return **Result;
}
void FBlueprintNativeCodeGenModule::GenerateSingleStub(UBlueprint* BP, const TCHAR* PlatformName)
{
UClass* Class = BP ? BP->GeneratedClass : nullptr;
if (!ensure(Class))
{
return;
}
// no PCHFilename should be necessary
const IAssetRegistry& Registry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
FAssetData AssetInfo = Registry.GetAssetByObjectPath(*Class->GetPathName());
FString FileContents;
TUniquePtr<IBlueprintCompilerCppBackend> Backend_CPP(IBlueprintCompilerCppBackendModuleInterface::Get().Create());
// Apparently we can only generate wrappers for classes, so any logic that results in non classes requesting
// wrappers will fail here:
FileContents = Backend_CPP->GenerateWrapperForClass(Class);
if (!FileContents.IsEmpty())
{
FFileHelper::SaveStringToFile(FileContents, *(GetManifest(PlatformName).CreateUnconvertedDependencyRecord(AssetInfo.PackageName, AssetInfo).GeneratedWrapperPath));
}
// The stub we generate still may have dependencies on other modules, so make sure the module dependencies are
// still recorded so that the .build.cs is generated correctly. Without this you'll get include related errors
// (or possibly linker errors) in stub headers:
GetManifest(PlatformName).GatherModuleDependencies(BP->GetOutermost());
}
void FBlueprintNativeCodeGenModule::GenerateSingleAsset(UField* ForConversion, const TCHAR* PlatformName, TSharedPtr<FNativizationSummary> NativizationSummary)
{
IBlueprintCompilerCppBackendModule& BackEndModule = (IBlueprintCompilerCppBackendModule&)IBlueprintCompilerCppBackendModule::Get();
auto& BackendPCHQuery = BackEndModule.OnPCHFilenameQuery();
const IAssetRegistry& Registry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
FAssetData AssetInfo = Registry.GetAssetByObjectPath(*ForConversion->GetPathName());
FBlueprintNativeCodeGenPaths TargetPaths = GetManifest(PlatformName).GetTargetPaths();
BackendPCHQuery.BindLambda([TargetPaths]()->FString
{
return TargetPaths.RuntimePCHFilename();
});
FConvertedAssetRecord& ConversionRecord = GetManifest(PlatformName).CreateConversionRecord(*ForConversion->GetPathName(), AssetInfo);
TSharedPtr<FString> HeaderSource(new FString());
TSharedPtr<FString> CppSource(new FString());
FBlueprintNativeCodeGenUtils::GenerateCppCode(ForConversion, HeaderSource, CppSource, NativizationSummary);
bool bSuccess = !HeaderSource->IsEmpty() || !CppSource->IsEmpty();
// Run the cpp first, because we cue off of the presence of a header for a valid conversion record (see
// FConvertedAssetRecord::IsValid)
if (!CppSource->IsEmpty())
{
if (!FFileHelper::SaveStringToFile(*CppSource, *ConversionRecord.GeneratedCppPath))
{
bSuccess &= false;
ConversionRecord.GeneratedCppPath.Empty();
}
CppSource->Empty(CppSource->Len());
}
else
{
ConversionRecord.GeneratedCppPath.Empty();
}
if (bSuccess && !HeaderSource->IsEmpty())
{
if (!FFileHelper::SaveStringToFile(*HeaderSource, *ConversionRecord.GeneratedHeaderPath))
{
bSuccess &= false;
ConversionRecord.GeneratedHeaderPath.Empty();
}
HeaderSource->Empty(HeaderSource->Len());
}
else
{
ConversionRecord.GeneratedHeaderPath.Empty();
}
if (ensure(bSuccess))
{
GetManifest(PlatformName).GatherModuleDependencies(ForConversion->GetOutermost());
}
else
{
UE_LOG(LogBlueprintCodeGen, Error, TEXT("FBlueprintNativeCodeGenModule::GenerateSingleAsset error: %s"), *GetPathNameSafe(ForConversion));
}
BackendPCHQuery.Unbind();
}
void FBlueprintNativeCodeGenModule::GenerateStubs()
{
TSet<TAssetPtr<UBlueprint>> AlreadyGenerated;
while (AlreadyGenerated.Num() < StubsRequiredByGeneratedCode.Num())
{
const int32 OldGeneratedNum = AlreadyGenerated.Num();
for (TAssetPtr<UBlueprint>& BPPtr : StubsRequiredByGeneratedCode)
{
bool bAlreadyGenerated = false;
AlreadyGenerated.Add(BPPtr, &bAlreadyGenerated);
if (bAlreadyGenerated)
{
continue;
}
ensureMsgf(AllPotentialStubs.Contains(BPPtr), TEXT("A required blueprint doesn't generate stub: %s"), *BPPtr.ToString());
for (auto& PlatformName : TargetPlatformNames)
{
GenerateSingleStub(BPPtr.LoadSynchronous(), *PlatformName);
}
}
if (!ensure(OldGeneratedNum != AlreadyGenerated.Num()))
{
break;
}
}
UE_LOG(LogBlueprintCodeGen, Log, TEXT("GenerateStubs - all unconverted bp: %d, generated wrapers: %d"), AllPotentialStubs.Num(), StubsRequiredByGeneratedCode.Num());
}
void FBlueprintNativeCodeGenModule::Convert(UPackage* Package, ESavePackageResult CookResult, const TCHAR* PlatformName)
{
// Find the struct/enum to convert:
UStruct* Struct = nullptr;
UEnum* Enum = nullptr;
GetFieldFormPackage(Package, Struct, Enum);
// First we gather information about bound functions.
UClass* AsClass = Cast<UClass>(Struct);
UBlueprint* BP = AsClass ? Cast<UBlueprint>(AsClass->ClassGeneratedBy) : nullptr;
if (BP)
{
CollectBoundFunctions(BP);
}
if (CookResult != ESavePackageResult::ReplaceCompletely && CookResult != ESavePackageResult::GenerateStub)
{
// nothing to convert
return;
}
if (Struct == nullptr && Enum == nullptr)
{
ensure(false);
return;
}
if (CookResult == ESavePackageResult::GenerateStub)
{
if (ensure(BP))
{
ensure(!ToGenerate.Contains(BP));
AllPotentialStubs.Add(BP);
}
}
else
{
check(CookResult == ESavePackageResult::ReplaceCompletely);
if (AsClass)
{
if (ensure(BP))
{
ensure(!AllPotentialStubs.Contains(BP));
ToGenerate.Add(BP);
}
}
else
{
UField* ForConversion = Struct ? (UField*)Struct : (UField*)Enum;
GenerateSingleAsset(ForConversion, PlatformName);
}
}
}
void FBlueprintNativeCodeGenModule::SaveManifest(int32 Id )
{
for (auto& PlatformName : TargetPlatformNames)
{
GetManifest(*PlatformName).Save(Id);
}
}
void FBlueprintNativeCodeGenModule::MergeManifest(int32 ManifestIdentifier)
{
for (auto& PlatformName : TargetPlatformNames)
{
FBlueprintNativeCodeGenManifest& CurrentManifest = GetManifest(*PlatformName);
FBlueprintNativeCodeGenManifest OtherManifest = FBlueprintNativeCodeGenManifest(CurrentManifest.GetTargetPaths().ManifestFilePath() + FString::FromInt(ManifestIdentifier));
CurrentManifest.Merge(OtherManifest);
}
}
void FBlueprintNativeCodeGenModule::FinalizeManifest()
{
for(auto& PlatformName : TargetPlatformNames)
{
GetManifest(*PlatformName).Save(-1);
check(FBlueprintNativeCodeGenUtils::FinalizePlugin(GetManifest(*PlatformName)));
}
}
UClass* FBlueprintNativeCodeGenModule::FindReplacedClassForObject(const UObject* Object) const
{
// we're only looking to replace class types:
if (Object && Object->IsA<UField>()
&& (EReplacementResult::ReplaceCompletely == IsTargetedForReplacement(Object)))
{
for (const UClass* Class = Object->GetClass(); Class; Class = Class->GetSuperClass())
{
if (Class == UUserDefinedEnum::StaticClass())
{
return UEnum::StaticClass();
}
if (Class == UUserDefinedStruct::StaticClass())
{
return UScriptStruct::StaticClass();
}
if (Class == UBlueprintGeneratedClass::StaticClass())
{
return UDynamicClass::StaticClass();
}
}
}
ensure(!Object || !(Object->IsA<UUserDefinedStruct>() || Object->IsA<UUserDefinedEnum>()));
return nullptr;
}
UObject* FBlueprintNativeCodeGenModule::FindReplacedNameAndOuter(UObject* Object, FName& OutName) const
{
OutName = NAME_None;
UObject* Outer = nullptr;
UActorComponent* ActorComponent = Cast<UActorComponent>(Object);
if (ActorComponent)
{
//if is child of a BPGC and not child of a CDO
UBlueprintGeneratedClass* BPGC = nullptr;
for (UObject* OuterObject = ActorComponent->GetOuter(); OuterObject && !BPGC; OuterObject = OuterObject->GetOuter())
{
if (OuterObject->HasAnyFlags(RF_ClassDefaultObject))
{
return Outer;
}
BPGC = Cast<UBlueprintGeneratedClass>(OuterObject);
}
for (UBlueprintGeneratedClass* SuperBPGC = BPGC; SuperBPGC && (OutName == NAME_None); SuperBPGC = Cast<UBlueprintGeneratedClass>(SuperBPGC->GetSuperClass()))
{
if (SuperBPGC->InheritableComponentHandler)
{
FComponentKey FoundKey = SuperBPGC->InheritableComponentHandler->FindKey(ActorComponent);
if (FoundKey.IsValid())
{
OutName = FoundKey.IsSCSKey() ? FoundKey.GetSCSVariableName() : ActorComponent->GetFName();
Outer = BPGC->GetDefaultObject(false);
break;
}
}
if (SuperBPGC->SimpleConstructionScript)
{
for (auto Node : SuperBPGC->SimpleConstructionScript->GetAllNodes())
{
if (Node->ComponentTemplate == ActorComponent)
{
OutName = Node->VariableName;
if (OutName != NAME_None)
{
Outer = BPGC->GetDefaultObject(false);
break;
}
}
}
}
}
}
if (Outer && (EReplacementResult::ReplaceCompletely == IsTargetedForReplacement(Object->GetClass())))
{
UE_LOG(LogBlueprintCodeGen, Log, TEXT("Object '%s' has replaced name '%s' and outer: '%s'"), *GetPathNameSafe(Object), *OutName.ToString(), *GetPathNameSafe(Outer));
return Outer;
}
return nullptr;
}
EReplacementResult FBlueprintNativeCodeGenModule::IsTargetedForReplacement(const UPackage* Package) const
{
// non-native packages with enums and structs should be converted, unless they are blacklisted:
UStruct* Struct = nullptr;
UEnum* Enum = nullptr;
GetFieldFormPackage(Package, Struct, Enum, RF_NoFlags);
UObject* Target = Struct;
if (Target == nullptr)
{
Target = Enum;
}
return IsTargetedForReplacement(Target);
}
EReplacementResult FBlueprintNativeCodeGenModule::IsTargetedForReplacement(const UObject* Object) const
{
const UStruct* Struct = Cast<UStruct>(Object);
const UEnum* Enum = Cast<UEnum>(Object);
if (Struct == nullptr && Enum == nullptr)
{
return EReplacementResult::DontReplace;
}
EReplacementResult Result = EReplacementResult::ReplaceCompletely;
if (const UClass* BlueprintClass = Cast<UClass>(Struct))
{
if (UBlueprint* Blueprint = Cast<UBlueprint>(BlueprintClass->ClassGeneratedBy))
{
const EBlueprintType UnconvertableBlueprintTypes[] = {
//BPTYPE_Const, // WTF is a "const" Blueprint?
BPTYPE_MacroLibrary,
BPTYPE_LevelScript,
};
EBlueprintType BlueprintType = Blueprint->BlueprintType;
for (int32 TypeIndex = 0; TypeIndex < ARRAY_COUNT(UnconvertableBlueprintTypes); ++TypeIndex)
{
if (BlueprintType == UnconvertableBlueprintTypes[TypeIndex])
{
Result = EReplacementResult::GenerateStub;
}
}
static const FBoolConfigValueHelper DontNativizeDataOnlyBP(TEXT("BlueprintNativizationSettings"), TEXT("bDontNativizeDataOnlyBP"));
if (DontNativizeDataOnlyBP)
{
if (FBlueprintEditorUtils::IsDataOnlyBlueprint(Blueprint))
{
return EReplacementResult::DontReplace;
}
}
for (UBlueprintGeneratedClass* ParentClassIt = Cast<UBlueprintGeneratedClass>(BlueprintClass->GetSuperClass())
; ParentClassIt; ParentClassIt = Cast<UBlueprintGeneratedClass>(ParentClassIt->GetSuperClass()))
{
EReplacementResult ParentResult = IsTargetedForReplacement(ParentClassIt);
if (ParentResult != EReplacementResult::ReplaceCompletely)
{
Result = EReplacementResult::GenerateStub;
}
}
for (TAssetSubclassOf<UBlueprint> ExcludedBlueprintTypeAsset : ExcludedBlueprintTypes)
{
UClass* ExcludedBPClass = ExcludedBlueprintTypeAsset.Get();
if (!ExcludedBPClass)
{
ExcludedBPClass = ExcludedBlueprintTypeAsset.LoadSynchronous();
}
if (ExcludedBPClass && Blueprint->IsA(ExcludedBPClass))
{
Result = EReplacementResult::GenerateStub;
}
}
}
}
auto IsObjectFromDeveloperPackage = [](const UObject* Obj) -> bool
{
return Obj && Obj->GetOutermost()->HasAllPackagesFlags(PKG_Developer);
};
auto IsDeveloperObject = [&](const UObject* Obj) -> bool
{
if (Obj)
{
if (IsObjectFromDeveloperPackage(Obj))
{
return true;
}
const UStruct* StructToTest = Obj->IsA<UStruct>() ? CastChecked<const UStruct>(Obj) : Obj->GetClass();
for (; StructToTest; StructToTest = StructToTest->GetSuperStruct())
{
if (IsObjectFromDeveloperPackage(StructToTest))
{
return true;
}
}
}
return false;
};
if (Object && (IsEditorOnlyObject(Object) || IsDeveloperObject(Object)))
{
UE_LOG(LogBlueprintCodeGen, Warning, TEXT("Object %s depends on Editor or Development stuff. Is shouldn't be cooked."), *GetPathNameSafe(Object));
return EReplacementResult::DontReplace;
}
// check blacklists:
// we can't use FindObject, because we may be converting a type while saving
if (Enum && ExcludedAssetTypes.Find(Enum->GetPathName()) != INDEX_NONE)
{
Result = EReplacementResult::GenerateStub;
}
while (Struct)
{
if (ExcludedAssetTypes.Find(Struct->GetPathName()) != INDEX_NONE)
{
Result = EReplacementResult::GenerateStub;
}
Struct = Struct->GetSuperStruct();
}
if (ExcludedAssets.Contains(Object->GetOutermost()))
{
Result = EReplacementResult::GenerateStub;
}
return Result;
}
IMPLEMENT_MODULE(FBlueprintNativeCodeGenModule, BlueprintNativeCodeGen);