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

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

Change 3256692 on 2017/01/13 by Ben.Cosh

	This updates the blueprint variable config option tooltip to be more accurate when describing the config to override the value in.
	#Jira UE-40334 - Blueprint variables report the wrong config file in the UI when using the set from config option
	#Proj Kismet

Change 3258553 on 2017/01/16 by Phillip.Kavan

	[UE-40131] Revised fix that will work for "inclusive" BP nativization with data-only BPs.

	- Mirrored from //UE4/Release-4.15 (CL# 3258541).

	#jira UE-40131

Change 3258958 on 2017/01/16 by Mike.Beach

	Adopted fix from UDN - resolves problems with Blueprints which inherit from UDataAsset. Compiling/Reinstancing them was leaving the data asset packages empty.

Change 3259363 on 2017/01/16 by Mike.Beach

	Revising fix in UBlueprint::BeginCacheForCookedPlatformData(), saving off nativization data if the -nativizeAssets param is present (not just if it was enabled in packaging settings).

	#jira UE-40620

Change 3260722 on 2017/01/17 by Maciej.Mroz

	Exclude client-only BPGC from Server.

	#jira UE-39728

Change 3261420 on 2017/01/17 by Dan.Oconnor

	Final prototype of the GMinimalCompileOnLoad pass

Change 3262208 on 2017/01/18 by Dan.Oconnor

	SA fix

Change 3263168 on 2017/01/18 by Dan.Oconnor

	Tweak to minimal compile on load pass, doing node refreshing early because node refresh can trigger CDO generation (which cannot happen while reinstancing, unless the new class is ready)

Change 3263844 on 2017/01/19 by Maciej.Mroz

	Error when CDO is created for an incomplete dynamic class

Change 3264647 on 2017/01/19 by Mike.Beach

	PR #3146: Only ensure if ptr != nullptr (Contributed by projectgheist )

	#jira UE-40846

Change 3264818 on 2017/01/19 by Dan.Oconnor

	Undo overzealous use of GMinimalCompileOnLoad

Change 3265075 on 2017/01/19 by Dan.Oconnor

	Make sure we use the authoritative class for the component template, fixes reinst issue in GMinimalCompileOnLoad pass

Change 3265085 on 2017/01/19 by Mike.Beach

	Mirroring CL 3260397

	Making it so CreateEvent nodes are searchable using the function name they are bound to (as requested by Jeff Farris). To find this data across your content library, in unopened Blueprints, you'll have to resave those Blueprints (so the new data is available in the asset registry).

Change 3272401 on 2017/01/25 by Mike.Beach

	Fixed a bug where modifying certain timeline settings would not dirty the Blueprint.

	PR #3137: UE-40674: Mark TimeLine BP as modified (Contributed by projectgheist)

	#jira UE-40680, UE-40674

Change 3273050 on 2017/01/26 by Maciej.Mroz

	#jira ODIN-4913, UE-40974
	merged cl3268193 from Odin branch

	Nativization:
	- added ConstructTInlineValue function
	- TInlineValue structures will be initialized using UScriptStruct::InitializeStruct, instead of callind default constructor ( default constructor is unrealible)

Change 3273052 on 2017/01/26 by Maciej.Mroz

	#jira UE-40917
	merged cl#3270456 from Odin branch

	Nativization:
	Actor template from ChildActorComponent is a subobject of nativized class.

Change 3275646 on 2017/01/27 by Dan.Oconnor

	Fix obscure issue when reinstancing interface types

Change 3275811 on 2017/01/27 by Dan.Oconnor

	ImplementsGetWorld now properly inherited for blueprint types that derive from a blueprint type that derives from a native type that "ImplementsGetWorld"

Change 3275922 on 2017/01/27 by Dan.Oconnor

	Preserve trashed properties' names, don't force regen nodes when using the new compile manager (it has already regen'd nodes)

Change 3276037 on 2017/01/27 by Dan.Oconnor

	Uses Authoritative class for this type check, added const version of GetAuthoritativeClass

Change 3276109 on 2017/01/27 by Phillip.Kavan

	[UE-40894] Fix data loss issues with non-native Blueprint classes that override inherited component default values from a nativized parent Blueprint class hierarchy.

	change summary:
	- modified FBlueprintEditorUtils::BuildComponentInstancingData() to accept an additional parameter indicating whether or not to use the CDO or the template's Archetype as the basis for building the delta property list.
	- revised UBlueprint::BeginCacheForCookedPlatformData() to first generate "override" data for ICH records that override SCS nodes from parent classes that are targeted for nativization. this also makes the optimized component instancing feature compatible with nativization as well (should that ever become useful).
	- revised UBlueprintGeneratedClass::CheckAndApplyComponentTemplateOverrides() to utilize the additional delta property list data that's now generated at cook time along with a delta serialization pass against the ICH template that's serializes changed properties to a cached data block at load time. this cached "override" data is then applied to instanced subobjects inherited from nativized parent Blueprint classes using a minimal binary serialization pass.

	#jira UE-40894

Change 3277671 on 2017/01/30 by Mike.Beach

	Mirroring CL 3276602.

	Refactoring FBlueprintCompilerCppBackend::SortNodesInUberGraphExecutionGroup() a bit. Catching cases that weren't acounted for - detecting cyclical logic now when we've pulled a node/statement out of order, and other nodes need to fall through to that logic (not relying on a goto).

	#jira UE-41188, UE-41189, UE-41186, UE-41037

Change 3278454 on 2017/01/30 by Mike.Beach

	Mirroring CL 3278054

	Upgrading USimpleConstructionScript::FixupSceneNodeHierarchy() so that the SCS detects orphaned components, and adds them to the hierarchy (notifying users of the issue with a warning).

	#jira UE-41247

Change 3278814 on 2017/01/31 by Maciej.Mroz

	fixed CIS warning
	fixed FBlueprintNativeCodeGenModule::FindReplacedNameAndOuter

Change 3279398 on 2017/01/31 by Mike.Beach

	Back out changelist 3278454 - causing unforseen issues, restructuring the component hierarchy.

Change 3282200 on 2017/02/01 by Dan.Oconnor

	Moved ForceLoad helpers into UBlueprint so that new compile on load path can prod the linker into loading stuff before flushing the compilation queue

Change 3282269 on 2017/02/01 by Mike.Beach

	ClassGeneratedBy not dependable in cooked builds, must rely on class flags to determine if this is a Blueprint class.

Change 3284455 on 2017/02/02 by Dan.Oconnor

	Reinstancer optimizations and skipping loader reset when using the new compile manager

Change 3284463 on 2017/02/02 by Dan.Oconnor

	Handling missing GeneratedClass

Change 3284476 on 2017/02/02 by Dan.Oconnor

	Flush compilation manager when we would normally do a bytecode pass, this guarantees clients of LoadObject that we won't reinstance a pointer on load (giving them back a stale object)

Change 3284523 on 2017/02/02 by Dan.Oconnor

	Optimize FBlueprintUnloader::ReplaceStaleRefs, add option to skip reinstancing when recompiling bytecode, refactored bytecode recompilation options into a flag parameter

Change 3285731 on 2017/02/03 by Dan.Oconnor

	Merging //UE4/Dev-Main to Dev-Blueprints (//UE4/Dev-Blueprints)

	Auto resolved, with merging:
	K2Node_CreateDelegate.h/cpp, KismetCompiler.cpp, WidgetBlueprintCompiler.cpp, Kismet2.cpp, KismetReinstanceUtilities.cpp/h, KismetEditorUtils.h, BlueprintSupport.cpp, Class.cpp, LinkerLoad.cpp, Obj.cpp, Class.h, Object.h, BlueprintGeneratedClass.cpp, DefaultEngine.ini,

	Manually resolved:
	BlueprintEditorUtils.cpp, TestRunner.Automation.Tests.cs, OrionAutobuyCardStatEntry.cpp/h, OrionStateWidget_DraftLobby.cpp

	All else resolved safely (no merging)

Change 3286019 on 2017/02/03 by Dan.Oconnor

	Add assertion that was added in main

Change 3286031 on 2017/02/03 by Dan.Oconnor

	Build fix, missing include

Change 3286056 on 2017/02/03 by Mike.Beach

	Corrected fix (from CL 3278454, which had to be backed out) - USimpleConstructionScript::FixupSceneNodeHierarchy() so that the SCS detects orphaned components, and adds them to the hierarchy (notifying users of the issue with a warning).

	#jira UE-41247

Change 3286302 on 2017/02/03 by Dan.Oconnor

	Allow reinstancing to run very early or in other situation where GEditor is not yet set up

Change 3286386 on 2017/02/03 by Dan.Oconnor

	GMinimalCompileOnLoad checks no longer needed with my local changes to BlueprintCompilationManager

Change 3286391 on 2017/02/03 by Dan.Oconnor

	Missed old comment

Change 3286614 on 2017/02/03 by Dan.Oconnor

	GMinimalCompileOnLoad logic no longer needed

Change 3286655 on 2017/02/03 by Dan.Oconnor

	Refactor FKismetEditorUtilities::CompileBlueprint flags into EBlueprintCompileOptions

Change 3286662 on 2017/02/03 by Dan.Oconnor

	Build fix, missed file

Change 3288770 on 2017/02/06 by Mike.Beach

	Attempt to fix up CIS warnings from CL 3286056.

Change 3289236 on 2017/02/06 by Mike.Beach

	Missed fix for CIS warning.

Change 3289276 on 2017/02/06 by Dan.Oconnor

	Duplication of CDO is unnecessary
	#fyi Maciej.Mroz

Change 3289334 on 2017/02/06 by Dan.Oconnor

	Add option to skip all reinstancing logic when running CompileBlueprint - used locally by the blueprint compilation manager. Fixed Typo.

Change 3289443 on 2017/02/06 by Dan.Oconnor

	Further cleanup of GMinimalCompileOnLoad abuse. added flag to skip reinstancing and "stub on failure" compile pass

Change 3290509 on 2017/02/07 by Dan.Oconnor

	Addressed this with a change to the blueprint compiler manager - no modification needed

Change 3290534 on 2017/02/07 by Dan.Oconnor

	Remove old forward declare and outdated comment

Change 3291446 on 2017/02/07 by Dan.Oconnor

	This CPFUO call is unused with my latest iteration on the compiler manager

Change 3291516 on 2017/02/07 by Dan.Oconnor

	Running compile manager earlier means we don't need to worry about this weird (and costly) call

Change 3291534 on 2017/02/07 by Dan.Oconnor

	This compile children call is no longer running with latest improvents to the compile manager

Change 3292587 on 2017/02/08 by Maciej.Mroz

	#jira UE-41694
	Mirroring cl#3292273 from Odin branch

Change 3293344 on 2017/02/08 by Dan.Oconnor

	Iteration on blueprint compile manager - fortnite loads without issue. For now I do the three compile passes that are done by the existing load paths. Timing is actually quite reasonable. Centralizing reinstancing has sped things up.

	#fyi Maciej.Mroz

Change 3293565 on 2017/02/08 by Dan.Oconnor

	Back out changelist 3293344 -  wrong CL

Change 3293567 on 2017/02/08 by Dan.Oconnor

	Iteration on blueprint compile manager - fortnite loads without issue. For now I do the three compile passes that are done by the existing load paths. Timing is actually quite reasonable. Centralizing reinstancing has sped things up.

	#fyi Maciej.Mroz

Change 3294334 on 2017/02/09 by Phillip.Kavan

	[UE-41246] Fix misaligned memory access of noexport struct properties leading to incorrectly initialized values.

	- Mirrored from //Odin/Main/... (CL# 3284308).

	#jira UE-41246

Change 3294343 on 2017/02/09 by Phillip.Kavan

	[UE-41246] Add a whitelist mechanism to handle native noexport types that can support direct field access in nativized Blueprint code.

	- Mirrored from //Odin/Main/... (CL# 3285789).

	#jira UE-41246

Change 3295913 on 2017/02/09 by Dan.Oconnor

	Back out reinstancer optimization, failing to find a reference and it's causing a crash when a component is renamed
	#jira UE-41843

Change 3297279 on 2017/02/10 by Dan.Oconnor

	SA fix

Change 3297587 on 2017/02/10 by Mike.Beach

	Temporarily disabling a spurious warning that gets triggered in Fortnite, when correctly excluding client-only Blueprints.

	#jira UE-41880

Change 3298078 on 2017/02/10 by Dan.Oconnor

	Try to suppress confused SA warning

[CL 3298251 by Mike Beach in Main branch]
2017-02-10 19:50:34 -05:00

941 lines
31 KiB
C++

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "BlueprintNativeCodeGenModule.h"
#include "Engine/Blueprint.h"
#include "HAL/FileManager.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Misc/ConfigCacheIni.h"
#include "UObject/UObjectHash.h"
#include "UObject/Package.h"
#include "Templates/Greater.h"
#include "Components/ActorComponent.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "AssetData.h"
#include "Engine/UserDefinedEnum.h"
#include "Engine/UserDefinedStruct.h"
#include "Settings/ProjectPackagingSettings.h"
#include "AssetRegistryModule.h"
#include "BlueprintNativeCodeGenManifest.h"
#include "Blueprint/BlueprintSupport.h"
#include "BlueprintCompilerCppBackendInterface.h"
#include "IBlueprintCompilerCppBackendModule.h"
#include "BlueprintNativeCodeGenUtils.h"
#include "Engine/SCS_Node.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Engine/InheritableComponentHandler.h"
#include "Animation/AnimBlueprint.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;
FFileHelper::EEncodingOptions::Type ForcedEncoding() const
{
return FFileHelper::EEncodingOptions::ForceUTF8;
}
protected:
virtual void Initialize(const FNativeCodeGenInitData& InitData) override;
virtual void InitializeForRerunDebugOnly(const TArray<FPlatformNativizationDetails>& 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;
// Children of these classes won't be nativized
TArray<FString> ExcludedAssetTypes;
// Eg: +ExcludedBlueprintTypes=/Script/Engine.AnimBlueprint
TArray<TAssetSubclassOf<UBlueprint>> ExcludedBlueprintTypes;
// Individually excluded assets
TSet<FStringAssetReference> ExcludedAssets;
// Excluded folders. It excludes only BPGCs, enums and structures are still converted.
TArray<FString> ExcludedFolderPaths;
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.
// Cached values from IsTargetedForReplacement
mutable TMap<FStringAssetReference, EReplacementResult> CachedIsTargetedForReplacement;
};
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));
}
GConfig->GetArray(TEXT("BlueprintNativizationSettings"), TEXT("ExcludedFolderPaths"), ExcludedFolderPaths, GEditorIni);
}
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)
{
CachedIsTargetedForReplacement.Reset();
ReadConfig();
IBlueprintNativeCodeGenCore::Register(this);
// Each platform will need a manifest, because each platform could cook different assets:
for (const FPlatformNativizationDetails& Platform : InitData.CodegenTargets)
{
FString OutputPath = FPaths::Combine(*Platform.PlatformTargetDirectory, NativizationCookControllerImpl::DefaultPluginName);
Manifests.Add(*Platform.PlatformName, TUniquePtr<FBlueprintNativeCodeGenManifest>(new FBlueprintNativeCodeGenManifest(NativizationCookControllerImpl::DefaultPluginName, OutputPath, Platform.CompilerNativizationOptions)));
TargetPlatformNames.Add(Platform.PlatformName);
// Clear source code folder
const FString SourceCodeDir = GetManifest(*Platform.PlatformName).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<FPlatformNativizationDetails>& CodegenTargets)
{
CachedIsTargetedForReplacement.Reset();
ReadConfig();
IBlueprintNativeCodeGenCore::Register(this);
FillTargetedForReplacementQuery();
FillIsFunctionUsedInADelegate();
for (const FPlatformNativizationDetails& Platform : CodegenTargets)
{
// load the old manifest:
FString OutputPath = FPaths::Combine(*Platform.PlatformTargetDirectory, NativizationCookControllerImpl::DefaultPluginName, *FBlueprintNativeCodeGenPaths::GetDefaultManifestPath());
Manifests.Add(Platform.PlatformName, 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.PlatformName).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.PlatformName);
}
// reconvert every unconverted dependency listed in the manifest:
for (const auto& ConversionTarget : GetManifest(*Platform.PlatformName).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.PlatformName);
}
}
for (TAssetPtr<UBlueprint>& BPPtr : ToGenerate)
{
UBlueprint* BP = BPPtr.LoadSynchronous();
if (ensure(BP))
{
GenerateSingleAsset(BP->GeneratedClass, *Platform.PlatformName);
}
}
}
}
void FBlueprintNativeCodeGenModule::GenerateFullyConvertedClasses()
{
TSharedPtr<FNativizationSummary> NativizationSummary(new FNativizationSummary());
{
IBlueprintCompilerCppBackendModule& CodeGenBackend = (IBlueprintCompilerCppBackendModule&)IBlueprintCompilerCppBackendModule::Get();
CodeGenBackend.NativizationSummary() = NativizationSummary;
}
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, Display, TEXT("Nativization Summary - Inaccessible Properties:"));
NativizationSummary->InaccessiblePropertyStat.ValueSort(TGreater<int32>());
for (auto& Iter : NativizationSummary->InaccessiblePropertyStat)
{
UE_LOG(LogBlueprintCodeGen, Display, TEXT("\t %s \t - %d"), *Iter.Key.ToString(), Iter.Value);
}
}
{
UE_LOG(LogBlueprintCodeGen, Display, TEXT("Nativization Summary - AnimBP:"));
UE_LOG(LogBlueprintCodeGen, Display, TEXT("Name, Children, Non-empty Functions (Empty Functions), Variables, FunctionUsage, VariableUsage"));
for (auto& Iter : NativizationSummary->AnimBlueprintStat)
{
UE_LOG(LogBlueprintCodeGen, Display
, TEXT("%s, %d, %d (%d), %d, %d, %d")
, *Iter.Key.ToString()
, Iter.Value.Children
, Iter.Value.Functions - Iter.Value.ReducibleFunctions
, Iter.Value.ReducibleFunctions
, Iter.Value.Variables
, Iter.Value.FunctionUsage
, Iter.Value.VariableUsage);
}
}
UE_LOG(LogBlueprintCodeGen, Display, TEXT("Nativization Summary - Shared Variables From Graph: %d"), NativizationSummary->MemberVariablesFromGraph);
}
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)
{
if (!ensure(BP))
{
return;
}
UClass* Class = BP->GeneratedClass;
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)
, ForcedEncoding());
}
// 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, GetManifest(PlatformName).GetCompilerNativizationOptions());
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, ForcedEncoding()))
{
bSuccess &= false;
ConversionRecord.GeneratedCppPath.Empty();
}
CppSource->Empty(CppSource->Len());
}
else
{
ConversionRecord.GeneratedCppPath.Empty();
}
if (bSuccess && !HeaderSource->IsEmpty())
{
if (!FFileHelper::SaveStringToFile(*HeaderSource, *ConversionRecord.GeneratedHeaderPath, ForcedEncoding()))
{
bSuccess &= false;
ConversionRecord.GeneratedHeaderPath.Empty();
}
HeaderSource->Empty(HeaderSource->Len());
}
else
{
ConversionRecord.GeneratedHeaderPath.Empty();
}
if (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;
auto GetOuterBPGC = [](UObject* FirstOuter) -> UBlueprintGeneratedClass*
{
UBlueprintGeneratedClass* BPGC = nullptr;
for (UObject* OuterObject = FirstOuter; OuterObject && !BPGC; OuterObject = OuterObject->GetOuter())
{
if (OuterObject->HasAnyFlags(RF_ClassDefaultObject))
{
return nullptr;
}
BPGC = Cast<UBlueprintGeneratedClass>(OuterObject);
}
return BPGC;
};
UActorComponent* ActorComponent = Cast<UActorComponent>(Object);
if (ActorComponent)
{
//if is child of a BPGC and not child of a CDO
UBlueprintGeneratedClass* BPGC = GetOuterBPGC(ActorComponent->GetOuter());
FName NewName = NAME_None;
UObject* OuterCDO = nullptr;
for (UBlueprintGeneratedClass* SuperBPGC = BPGC; SuperBPGC && (NewName == NAME_None); SuperBPGC = Cast<UBlueprintGeneratedClass>(SuperBPGC->GetSuperClass()))
{
if (SuperBPGC->InheritableComponentHandler)
{
FComponentKey FoundKey = SuperBPGC->InheritableComponentHandler->FindKey(ActorComponent);
if (FoundKey.IsValid())
{
NewName = FoundKey.IsSCSKey() ? FoundKey.GetSCSVariableName() : ActorComponent->GetFName();
OuterCDO = BPGC->GetDefaultObject(false);
break;
}
}
if (SuperBPGC->SimpleConstructionScript)
{
for (auto Node : SuperBPGC->SimpleConstructionScript->GetAllNodes())
{
if (Node->ComponentTemplate == ActorComponent)
{
NewName = Node->GetVariableName();
if (NewName != NAME_None)
{
OuterCDO = BPGC->GetDefaultObject(false);
break;
}
}
}
}
}
if (OuterCDO && (EReplacementResult::ReplaceCompletely == IsTargetedForReplacement(OuterCDO->GetClass())))
{
OutName = NewName;
UE_LOG(LogBlueprintCodeGen, Log, TEXT("Object '%s' has replaced name '%s' and outer: '%s'"), *GetPathNameSafe(Object), *OutName.ToString(), *GetPathNameSafe(OuterCDO));
return OuterCDO;
}
}
else
{
UChildActorComponent* OuterCAC = Cast<UChildActorComponent>(Object->GetOuter());
if (OuterCAC && OuterCAC->GetChildActorTemplate() == Object)
{
UBlueprintGeneratedClass* BPGC = GetOuterBPGC(OuterCAC->GetOuter());
if (BPGC && (EReplacementResult::ReplaceCompletely == IsTargetedForReplacement(BPGC)))
{
return BPGC;
}
}
}
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
{
if (Object == nullptr)
{
return EReplacementResult::DontReplace;
}
const UUserDefinedStruct* const UDStruct = Cast<const UUserDefinedStruct>(Object);
const UUserDefinedEnum* const UDEnum = Cast<const UUserDefinedEnum>(Object);
const UBlueprintGeneratedClass* const BlueprintClass = Cast<const UBlueprintGeneratedClass>(Object);
if (UDStruct == nullptr && UDEnum == nullptr && BlueprintClass == nullptr)
{
return EReplacementResult::DontReplace;
}
const FStringAssetReference ObjectKey(Object);
{
const EReplacementResult* const CachedValue = CachedIsTargetedForReplacement.Find(ObjectKey); //THe referenced returned by FindOrAdd could be invalid later, when filled.
if (CachedValue)
{
return *CachedValue;
}
}
const UBlueprint* const Blueprint = BlueprintClass ? Cast<UBlueprint>(BlueprintClass->ClassGeneratedBy) : nullptr;
const UProjectPackagingSettings* const PackagingSettings = GetDefault<UProjectPackagingSettings>();
const bool bNativizeOnlySelectedBPs = PackagingSettings && PackagingSettings->BlueprintNativizationMethod == EProjectPackagingBlueprintNativizationMethod::Exclusive;
auto ObjectIsNotReplacedAtAll = [&]() -> bool
{
// EDITOR ON DEVELOPMENT OBJECT
{
auto IsDeveloperObject = [](const UObject* Obj) -> bool
{
auto IsObjectFromDeveloperPackage = [](const UObject* InObj) -> bool
{
return InObj && InObj->GetOutermost()->HasAllPackagesFlags(PKG_Developer);
};
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. It shouldn't be cooked."), *GetPathNameSafe(Object));
return true;
}
}
// DATA ONLY BP
{
static const FBoolConfigValueHelper DontNativizeDataOnlyBP(TEXT("BlueprintNativizationSettings"), TEXT("bDontNativizeDataOnlyBP"));
if (DontNativizeDataOnlyBP && !bNativizeOnlySelectedBPs && Blueprint && FBlueprintEditorUtils::IsDataOnlyBlueprint(Blueprint))
{
return true;
}
}
// Don't convert objects like Default__WidgetBlueprintGeneratedClass
if (Object && (Object->HasAnyFlags(RF_ClassDefaultObject)))
{
return true;
}
return false;
};
if (ObjectIsNotReplacedAtAll())
{
CachedIsTargetedForReplacement.Add(ObjectKey, EReplacementResult::DontReplace);
return EReplacementResult::DontReplace;
}
auto ObjectGenratesOnlyStub = [&]() -> bool
{
// ExcludedFolderPaths
if (BlueprintClass)
{
const FString ObjPathName = Object->GetPathName();
for (const FString& ExcludedPath : ExcludedFolderPaths)
{
if (ObjPathName.StartsWith(ExcludedPath))
{
return true;
}
}
}
// ExcludedAssetTypes
{
// we can't use FindObject, because we may be converting a type while saving
if (UDEnum && ExcludedAssetTypes.Find(UDEnum->GetPathName()) != INDEX_NONE)
{
return true;
}
const UStruct* LocStruct = Cast<const UStruct>(Object);
while (LocStruct)
{
if (ExcludedAssetTypes.Find(LocStruct->GetPathName()) != INDEX_NONE)
{
return true;
}
LocStruct = LocStruct->GetSuperStruct();
}
}
// ExcludedAssets
{
if (ExcludedAssets.Contains(Object->GetOutermost()))
{
return true;
}
}
if (Blueprint && BlueprintClass)
{
// Reducible AnimBP
{
static const FBoolConfigValueHelper NativizeAnimBPOnlyWhenNonReducibleFuncitons(TEXT("BlueprintNativizationSettings"), TEXT("bNativizeAnimBPOnlyWhenNonReducibleFuncitons"));
if (NativizeAnimBPOnlyWhenNonReducibleFuncitons)
{
if (const UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(Blueprint))
{
ensure(AnimBlueprint->bHasBeenRegenerated);
if (AnimBlueprint->bHasAnyNonReducibleFunction == UBlueprint::EIsBPNonReducible::No)
{
UE_LOG(LogBlueprintCodeGen, Log, TEXT("AnimBP %s without non-reducible functions is excluded from nativization"), *GetPathNameSafe(Blueprint));
return true;
}
}
}
}
// Unconvertable Blueprint
{
const EBlueprintType UnconvertableBlueprintTypes[] = {
//BPTYPE_Const, // What is a "const" Blueprint?
BPTYPE_MacroLibrary,
BPTYPE_LevelScript,
};
const EBlueprintType BlueprintType = Blueprint->BlueprintType;
for (int32 TypeIndex = 0; TypeIndex < ARRAY_COUNT(UnconvertableBlueprintTypes); ++TypeIndex)
{
if (BlueprintType == UnconvertableBlueprintTypes[TypeIndex])
{
return true;
}
}
}
// ExcludedBlueprintTypes
for (TAssetSubclassOf<UBlueprint> ExcludedBlueprintTypeAsset : ExcludedBlueprintTypes)
{
UClass* ExcludedBPClass = ExcludedBlueprintTypeAsset.Get();
if (!ExcludedBPClass)
{
ExcludedBPClass = ExcludedBlueprintTypeAsset.LoadSynchronous();
}
if (ExcludedBPClass && Blueprint->IsA(ExcludedBPClass))
{
return true;
}
}
const bool bFlaggedForNativization = (Blueprint->NativizationFlag == EBlueprintNativizationFlag::Dependency) ?
PackagingSettings->IsBlueprintAssetInNativizationList(Blueprint) :
(Blueprint->NativizationFlag == EBlueprintNativizationFlag::ExplicitlyEnabled);
// Blueprint is not selected
if (bNativizeOnlySelectedBPs && !bFlaggedForNativization)
{
return true;
}
// Parent Class in not converted
for (const UBlueprintGeneratedClass* ParentClassIt = Cast<UBlueprintGeneratedClass>(BlueprintClass->GetSuperClass())
; ParentClassIt; ParentClassIt = Cast<UBlueprintGeneratedClass>(ParentClassIt->GetSuperClass()))
{
const EReplacementResult ParentResult = IsTargetedForReplacement(ParentClassIt);
if (ParentResult != EReplacementResult::ReplaceCompletely)
{
if (bNativizeOnlySelectedBPs)
{
UE_LOG(LogBlueprintCodeGen, Error, TEXT("BP %s is selected for nativization, but its parent class %s is not nativized."), *GetPathNameSafe(Blueprint), *GetPathNameSafe(ParentClassIt));
}
return true;
}
}
// Interface class not converted
TArray<UClass*> InterfaceClasses;
FBlueprintEditorUtils::FindImplementedInterfaces(Blueprint, false, InterfaceClasses);
for (const UClass* InterfaceClassIt : InterfaceClasses)
{
const UBlueprintGeneratedClass* InterfaceBPGC = Cast<const UBlueprintGeneratedClass>(InterfaceClassIt);
if (InterfaceBPGC)
{
const EReplacementResult InterfaceResult = IsTargetedForReplacement(InterfaceBPGC);
if (InterfaceResult != EReplacementResult::ReplaceCompletely)
{
if (bNativizeOnlySelectedBPs)
{
UE_LOG(LogBlueprintCodeGen, Error, TEXT("BP %s is selected for nativization, but BP interface class %s is not nativized."), *GetPathNameSafe(Blueprint), *GetPathNameSafe(InterfaceClassIt));
}
return true;
}
}
}
}
return false;
};
if (ObjectGenratesOnlyStub())
{
CachedIsTargetedForReplacement.Add(ObjectKey, EReplacementResult::GenerateStub);
return EReplacementResult::GenerateStub;
}
CachedIsTargetedForReplacement.Add(ObjectKey, EReplacementResult::ReplaceCompletely);
return EReplacementResult::ReplaceCompletely;
}
IMPLEMENT_MODULE(FBlueprintNativeCodeGenModule, BlueprintNativeCodeGen);