Files
UnrealEngineUWP/Engine/Source/Developer/BlueprintNativeCodeGen/Private/BlueprintNativeCodeGenUtils.cpp
Mike Beach 134cb04d27 Copying //UE4/Dev-Blueprints to Dev-Main (//UE4/Dev-Main) @ 2781164
#lockdown Nick.Penwarden

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

Change 2716841 on 2015/10/05 by Mike.Beach

	(WIP) Cleaning up how we setup script assets for replacement on cook (aligning more with the Blueprint conversion tool).

	#codereview Maciej.Mroz

Change 2719089 on 2015/10/07 by Maciej.Mroz

	ToValidCPPIdentifierChars handles propertly '?' char.

	#codereview Dan.Oconnor

Change 2719361 on 2015/10/07 by Maciej.Mroz

	Generated native code for AnimBPGC - some preliminary changes.

	Refactor: UAnimBlueprintGeneratedClass is not accessed directly in runtime. It is accessed via UAnimClassInterface interface.
	Properties USkeletalMeshComponent::AnimBlueprintGeneratedClass and UInterpTrackFloatAnimBPParam::AnimBlueprintClass were changed into "TSubclassOf<UAnimInstance> AnimClass"

	The UDynamicClass also can deliver the IAnimClassInterface interface. See UAnimClassData, IAnimClassInterface::GetFromClass and UDynamicClass::AnimClassImplementation.

	#codereview Lina.Halper, Thomas.Sarkanen

Change 2719383 on 2015/10/07 by Maciej.Mroz

	Debug-only code removed

Change 2720528 on 2015/10/07 by Dan.Oconnor

	Fix for determinsitc cooking of async tasks and load asset nodes
	#codereview Mike.Beach, Maciej.Mroz

Change 2721273 on 2015/10/08 by Maciej.Mroz

	Blueprint Compiler Cpp Backend
	- Anim Blueprints can be converted
	- Various fixes/improvements

Change 2721310 on 2015/10/08 by Maciej.Mroz

	refactor (cl#2719361) - no "auto" keyword

Change 2721727 on 2015/10/08 by Mike.Beach

	(WIP) Setup the cook commandlet so it handles converted assets, replacing them with generated classes.

	    - Refactored the conversion manifest (using a map over an array)
	    - Centralized destination paths into a helper struct (for the manifest)
	    - Generating an Editor module that automatically hooks into the cook process when enabled
	    - Loading and applying native replacments for the cook

Change 2723276 on 2015/10/09 by Michael.Schoell

	Blueprints duplicated for PIE will no longer register as dependencies to other Blueprint.

	#jira UE-16695 - Editor freezes then crashes while attempting to save during PIE
	#jira UE-21614 - [CrashReport] Crash while saving during PIE - FKismetEditorUtilities::CompileBlueprint() kismet2.cpp:736

Change 2724345 on 2015/10/11 by Ben.Cosh

	Blueprint profiler at first pass, this includes the ability to instrument specific blueprints with realtime editor stat display.
	#UEBP-21 - Profiling data capture and storage
	#UEBP-13 - Performance capture landing page
	#Branch UE4
	#Proj BlueprintProfiler, BlueprintGraph, EditorStyle, Kismet, UnrealEd, CoreUObject, Engine

Change 2724613 on 2015/10/12 by Ben.Cosh

	Incremental update for blueprint profiler to fix the way some of the reported stats cascade through events and branches and additionally some missed bits of code are refactored/removed.
	#Branch UE4
	#Proj BlueprintProfiler

	#info Whilst looking into this I spotted the reason why the stats seem so erratic, There appears to be an issue with FText's use of EXPERIMENTAL_TEXT_FAST_DECIMAL_FORMAT which I have reported, but ideally disable this locally until a fix is integrated.

Change 2724723 on 2015/10/12 by Maciej.Mroz

	Constructor of a dynamic class creates CDO.

	#codereview Robert.Manuszewski

Change 2725108 on 2015/10/12 by Mike.Beach

	[UE-21891] Minor fix to the array shuffle() function; now processes the last entry like all the others.

Change 2726358 on 2015/10/13 by Maciej.Mroz

	UDataTable is properly saved even if its RowStruct is null.

	https://udn.unrealengine.com/questions/264064/crash-using-hotreload-in-custom-datatable-cdo-clas.html

Change 2727395 on 2015/10/13 by Mike.Beach

	(WIP) Second pass on the Blueprint conversion pipeline; setting it up for more optimal (speedier) performance.
	    * Using stubs for replacements (rather than loading dynamic replacement).
	    * Giving the cook commandlet more control (so a conversion could be ran directly).
	    * Now logging replacements by old object path (to account for UPackage replacement queries).
	    * Fix for [UE-21947], unshelved from CL 2724944 (by Maciej.Mroz).

	#codereview Maciej.Mroz

Change 2727484 on 2015/10/13 by Mike.Beach

	[UE-22008] Fixing up comment/tooltip typo for UActorComponent::bAutoActivate.

Change 2727527 on 2015/10/13 by Mike.Beach

	Downgrading an inactionable EdGraph warning, while adding more info so we could possibly determine what's happening.

Change 2727702 on 2015/10/13 by Dan.Oconnor

	Fix for crash in UDelegateProperty::GetCPPType when called on a function with no OwnerClass (events)

Change 2727968 on 2015/10/14 by Maciej.Mroz

	Since ConstructorHelpers::FClassFinder is usually static, the loaded class should be in root set, to prevent the pointer stored in  ConstructorHelpers::FClassFinder from being obsolete.
	FindOrLoadClass behaves now like FindOrLoadObject.

	#codereview Robert.Manuszewski, Nick.Whiting

Change 2728139 on 2015/10/14 by Phillip.Kavan
2015-11-25 18:47:20 -05:00

392 lines
14 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "BlueprintNativeCodeGenPCH.h"
#include "BlueprintNativeCodeGenManifest.h"
#include "BlueprintNativeCodeGenUtils.h"
#include "NativeCodeGenCommandlineParams.h"
#include "Kismet2/KismetReinstanceUtilities.h" // for FBlueprintCompileReinstancer
#include "Kismet2/CompilerResultsLog.h"
#include "KismetCompilerModule.h"
#include "Engine/Blueprint.h"
#include "Engine/UserDefinedStruct.h"
#include "Engine/UserDefinedEnum.h"
#include "Kismet2/KismetEditorUtilities.h" // for CompileBlueprint()
#include "OutputDevice.h" // for GWarn
#include "GameProjectUtils.h" // for GenerateGameModuleBuildFile
#include "Editor/GameProjectGeneration/Public/GameProjectUtils.h" // for GenerateGameModuleBuildFile()
#include "App.h" // for GetGameName()
#include "BlueprintSupport.h" // for FReplaceCookedBPGC
#include "IBlueprintCompilerCppBackendModule.h" // for OnPCHFilenameQuery()
#include "BlueprintNativeCodeGenModule.h"
#include "ScopeExit.h"
#include "Editor/Kismet/Public/FindInBlueprintManager.h" // for FDisableGatheringDataOnScope
#include "Editor/UnrealEd/Public/Kismet2/BlueprintEditorUtils.h" // for FBlueprintEditorUtils::FForceFastBlueprintDuplicationScope
DEFINE_LOG_CATEGORY(LogBlueprintCodeGen)
/*******************************************************************************
* BlueprintNativeCodeGenUtilsImpl
******************************************************************************/
namespace BlueprintNativeCodeGenUtilsImpl
{
static FString CoreModuleName = TEXT("Core");
static FString EngineModuleName = TEXT("Engine");
static FString EngineHeaderFile = TEXT("Engine.h");
/**
* Deletes the files/directories in the supplied array.
*
* @param TargetPaths The set of directory and file paths that you want deleted.
* @return True if all the files/directories were successfully deleted, other wise false.
*/
static bool WipeTargetPaths(const FBlueprintNativeCodeGenPaths& TargetPaths);
/**
* Creates and fills out a new .uplugin file for the converted assets.
*
* @param PluginName The name of the plugin you're generating.
* @param TargetPaths Defines the file path/name for the plugin file.
* @return True if the file was successfully saved, otherwise false.
*/
static bool GeneratePluginDescFile(const FString& PluginName, const FBlueprintNativeCodeGenPaths& TargetPaths);
/**
* Creates a module implementation and header file for the converted assets'
* module (provides a IMPLEMENT_MODULE() declaration, which is required for
* the module to function).
*
* @param TargetPaths Defines the file path/name for the target files.
* @return True if the files were successfully generated, otherwise false.
*/
static bool GenerateModuleSourceFiles(const FBlueprintNativeCodeGenPaths& TargetPaths);
/**
* Creates and fills out a new .Build.cs file for the plugin's runtime module.
*
* @param Manifest Defines where the module file should be saved, what it should be named, etc..
* @return True if the file was successfully saved, otherwise false.
*/
static bool GenerateModuleBuildFile(const FBlueprintNativeCodeGenManifest& Manifest);
/**
* Determines what the expected native class will be for an asset that was
* or will be converted.
*
* @param ConversionRecord Identifies the asset's original type (which is used to infer the replacement type from).
* @return Either a class, enum, or struct class (depending on the asset's type).
*/
static UClass* ResolveReplacementType(const FConvertedAssetRecord& ConversionRecord);
}
//------------------------------------------------------------------------------
static bool BlueprintNativeCodeGenUtilsImpl::WipeTargetPaths(const FBlueprintNativeCodeGenPaths& TargetPaths)
{
IFileManager& FileManager = IFileManager::Get();
bool bSuccess = true;
bSuccess &= FileManager.Delete(*TargetPaths.PluginFilePath());
bSuccess &= FileManager.DeleteDirectory(*TargetPaths.PluginSourceDir(), /*RequireExists =*/false, /*Tree =*/true);
bSuccess &= FileManager.Delete(*TargetPaths.ManifestFilePath());
return bSuccess;
}
//------------------------------------------------------------------------------
static bool BlueprintNativeCodeGenUtilsImpl::GeneratePluginDescFile(const FString& PluginName, const FBlueprintNativeCodeGenPaths& TargetPaths)
{
FPluginDescriptor PluginDesc;
PluginDesc.FriendlyName = PluginName;
PluginDesc.CreatedBy = TEXT("Epic Games, Inc.");
PluginDesc.CreatedByURL = TEXT("http://epicgames.com");
PluginDesc.Description = TEXT("A programatically generated plugin which contains source files produced from Blueprint assets. The aim of this is to help performance by eliminating script overhead for the converted assets (using the source files in place of thier coresponding assets).");
PluginDesc.DocsURL = TEXT("@TODO");
PluginDesc.SupportURL = TEXT("https://answers.unrealengine.com/");
PluginDesc.Category = TEXT("Intermediate");
PluginDesc.bEnabledByDefault = true;
PluginDesc.bCanContainContent = false;
PluginDesc.bIsBetaVersion = true; // @TODO: change once we're confident in the feature
FModuleDescriptor RuntimeModuleDesc;
RuntimeModuleDesc.Name = *TargetPaths.RuntimeModuleName();
RuntimeModuleDesc.Type = EHostType::Runtime;
// load at startup (during engine init), after game modules have been loaded
RuntimeModuleDesc.LoadingPhase = ELoadingPhase::Default;
PluginDesc.Modules.Add(RuntimeModuleDesc);
FText ErrorMessage;
bool bSuccess = PluginDesc.Save(TargetPaths.PluginFilePath(), ErrorMessage);
if (!bSuccess)
{
UE_LOG(LogBlueprintCodeGen, Error, TEXT("Failed to generate the plugin description file: %s"), *ErrorMessage.ToString());
}
return bSuccess;
}
//------------------------------------------------------------------------------
static bool BlueprintNativeCodeGenUtilsImpl::GenerateModuleSourceFiles(const FBlueprintNativeCodeGenPaths& TargetPaths)
{
FText FailureReason;
TArray<FString> PchIncludes;
PchIncludes.Add(EngineHeaderFile);
bool bSuccess = GameProjectUtils::GeneratePluginModuleHeaderFile(TargetPaths.RuntimeModuleFile(FBlueprintNativeCodeGenPaths::HFile), PchIncludes, FailureReason);
if (bSuccess)
{
const FString NoStartupCode = TEXT("");
bSuccess &= GameProjectUtils::GeneratePluginModuleCPPFile(TargetPaths.RuntimeModuleFile(FBlueprintNativeCodeGenPaths::CppFile),
TargetPaths.RuntimeModuleName(), NoStartupCode, FailureReason);
}
if (!bSuccess)
{
UE_LOG(LogBlueprintCodeGen, Error, TEXT("Failed to generate module source files: %s"), *FailureReason.ToString());
}
return bSuccess;
}
//------------------------------------------------------------------------------
static bool BlueprintNativeCodeGenUtilsImpl::GenerateModuleBuildFile(const FBlueprintNativeCodeGenManifest& Manifest)
{
FModuleManager& ModuleManager = FModuleManager::Get();
TArray<FString> PublicDependencies;
// for IModuleInterface
PublicDependencies.Add(CoreModuleName);
// for Engine.h
PublicDependencies.Add(EngineModuleName);
if (GameProjectUtils::ProjectHasCodeFiles())
{
const FString GameModuleName = FApp::GetGameName();
if (ModuleManager.ModuleExists(*GameModuleName))
{
PublicDependencies.Add(GameModuleName);
}
}
TArray<FString> PrivateDependencies;
const TArray<UPackage*>& ModulePackages = Manifest.GetModuleDependencies();
PrivateDependencies.Reserve(ModulePackages.Num());
for (UPackage* ModulePkg : ModulePackages)
{
const FString PkgModuleName = FPackageName::GetLongPackageAssetName(ModulePkg->GetName());
if (ModuleManager.ModuleExists(*PkgModuleName))
{
PrivateDependencies.Add(PkgModuleName);
}
else
{
UE_LOG(LogBlueprintCodeGen, Warning, TEXT("Failed to find module for package: %s"), *PkgModuleName);
}
}
FBlueprintNativeCodeGenPaths TargetPaths = Manifest.GetTargetPaths();
FText ErrorMessage;
bool bSuccess = GameProjectUtils::GenerateGameModuleBuildFile(TargetPaths.RuntimeBuildFile(), TargetPaths.RuntimeModuleName(),
PublicDependencies, PrivateDependencies, ErrorMessage);
if (!bSuccess)
{
UE_LOG(LogBlueprintCodeGen, Error, TEXT("Failed to generate module build file: %s"), *ErrorMessage.ToString());
}
return bSuccess;
}
//------------------------------------------------------------------------------
static UClass* BlueprintNativeCodeGenUtilsImpl::ResolveReplacementType(const FConvertedAssetRecord& ConversionRecord)
{
const UClass* AssetType = ConversionRecord.AssetType;
check(AssetType != nullptr);
if (AssetType->IsChildOf<UUserDefinedEnum>())
{
return UEnum::StaticClass();
}
else if (AssetType->IsChildOf<UUserDefinedStruct>())
{
return UScriptStruct::StaticClass();
}
else if (AssetType->IsChildOf<UBlueprint>())
{
return UDynamicClass::StaticClass();
}
else
{
UE_LOG(LogBlueprintCodeGen, Error, TEXT("Unsupported asset type (%s); cannot determine replacement type."), *AssetType->GetName());
}
return nullptr;
}
/*******************************************************************************
* FBlueprintNativeCodeGenUtils
******************************************************************************/
//------------------------------------------------------------------------------
bool FBlueprintNativeCodeGenUtils::FinalizePlugin(const FBlueprintNativeCodeGenManifest& Manifest, const FNativeCodeGenCommandlineParams& CommandParams)
{
bool bSuccess = true;
bSuccess = bSuccess && BlueprintNativeCodeGenUtilsImpl::GenerateModuleBuildFile(Manifest);
bSuccess = bSuccess && BlueprintNativeCodeGenUtilsImpl::GenerateModuleSourceFiles(Manifest.GetTargetPaths());
bSuccess = bSuccess && BlueprintNativeCodeGenUtilsImpl::GeneratePluginDescFile(CommandParams.PluginName, Manifest.GetTargetPaths());
return bSuccess;
}
//------------------------------------------------------------------------------
void FBlueprintNativeCodeGenUtils::GenerateCppCode(UObject* Obj, TSharedPtr<FString> OutHeaderSource, TSharedPtr<FString> OutCppSource)
{
auto UDEnum = Cast<UUserDefinedEnum>(Obj);
auto UDStruct = Cast<UUserDefinedStruct>(Obj);
auto BPGC = Cast<UClass>(Obj);
auto InBlueprintObj = BPGC ? Cast<UBlueprint>(BPGC->ClassGeneratedBy) : Cast<UBlueprint>(Obj);
OutHeaderSource->Empty();
OutCppSource->Empty();
if (InBlueprintObj)
{
if (EBlueprintStatus::BS_Error == InBlueprintObj->Status)
{
UE_LOG(LogBlueprintCodeGen, Error, TEXT("Cannot convert \"%s\". It has errors."), *InBlueprintObj->GetPathName());
return;
}
check(InBlueprintObj->GetOutermost() != GetTransientPackage());
if (!ensureMsgf(InBlueprintObj->GeneratedClass, TEXT("Invalid generated class for %s"), *InBlueprintObj->GetName()))
{
return;
}
check(OutHeaderSource.IsValid());
check(OutCppSource.IsValid());
FDisableGatheringDataOnScope DisableFib;
const FString TempPackageName = FString::Printf(TEXT("/Temp/__TEMP_BP__/%s"), *InBlueprintObj->GetName());
UPackage* TempPackage = CreatePackage(nullptr, *TempPackageName);
check(TempPackage);
ON_SCOPE_EXIT
{
TempPackage->RemoveFromRoot();
TempPackage->MarkPendingKill();
};
UBlueprint* DuplicateBP = nullptr;
{
FBlueprintDuplicationScopeFlags BPDuplicationFlags(
FBlueprintDuplicationScopeFlags::NoExtraCompilation | FBlueprintDuplicationScopeFlags::TheSameTimelineGuid);
DuplicateBP = DuplicateObject<UBlueprint>(InBlueprintObj, TempPackage, *InBlueprintObj->GetName());
}
ensure((nullptr != DuplicateBP->GeneratedClass) && (InBlueprintObj->GeneratedClass != DuplicateBP->GeneratedClass));
ON_SCOPE_EXIT
{
DuplicateBP->RemoveFromRoot();
DuplicateBP->MarkPendingKill();
};
IBlueprintCompilerCppBackendModule& CodeGenBackend = (IBlueprintCompilerCppBackendModule&)IBlueprintCompilerCppBackendModule::Get();
CodeGenBackend.GetOriginalClassMap().Add(*DuplicateBP->GeneratedClass, *InBlueprintObj->GeneratedClass);
{
TSharedPtr<FBlueprintCompileReinstancer> Reinstancer = FBlueprintCompileReinstancer::Create(DuplicateBP->GeneratedClass);
IKismetCompilerInterface& Compiler = FModuleManager::LoadModuleChecked<IKismetCompilerInterface>(KISMET_COMPILER_MODULENAME);
TGuardValue<bool> GuardTemplateNameFlag(GCompilingBlueprint, true);
FCompilerResultsLog Results;
FKismetCompilerOptions CompileOptions;
CompileOptions.CompileType = EKismetCompileType::Cpp;
CompileOptions.OutCppSourceCode = OutCppSource;
CompileOptions.OutHeaderSourceCode = OutHeaderSource;
Compiler.CompileBlueprint(DuplicateBP, CompileOptions, Results);
Compiler.RemoveBlueprintGeneratedClasses(DuplicateBP);
}
if (EBlueprintType::BPTYPE_Interface == DuplicateBP->BlueprintType && OutCppSource.IsValid())
{
OutCppSource->Empty(); // ugly temp hack
}
}
else if ((UDEnum || UDStruct) && OutHeaderSource.IsValid())
{
IKismetCompilerInterface& Compiler = FModuleManager::LoadModuleChecked<IKismetCompilerInterface>(KISMET_COMPILER_MODULENAME);
if (UDEnum)
{
*OutHeaderSource = Compiler.GenerateCppCodeForEnum(UDEnum);
}
else if (UDStruct)
{
*OutHeaderSource = Compiler.GenerateCppCodeForStruct(UDStruct);
}
}
else
{
ensure(false);
}
}
/*******************************************************************************
* FScopedFeedbackContext
******************************************************************************/
//------------------------------------------------------------------------------
FBlueprintNativeCodeGenUtils::FScopedFeedbackContext::FScopedFeedbackContext()
: OldContext(GWarn)
, ErrorCount(0)
, WarningCount(0)
{
TreatWarningsAsErrors = GWarn->TreatWarningsAsErrors;
GWarn = this;
}
//------------------------------------------------------------------------------
FBlueprintNativeCodeGenUtils::FScopedFeedbackContext::~FScopedFeedbackContext()
{
GWarn = OldContext;
}
//------------------------------------------------------------------------------
bool FBlueprintNativeCodeGenUtils::FScopedFeedbackContext::HasErrors()
{
return (ErrorCount > 0) || (TreatWarningsAsErrors && (WarningCount > 0));
}
//------------------------------------------------------------------------------
void FBlueprintNativeCodeGenUtils::FScopedFeedbackContext::Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category)
{
switch (Verbosity)
{
case ELogVerbosity::Warning:
{
++WarningCount;
}
break;
case ELogVerbosity::Error:
case ELogVerbosity::Fatal:
{
++ErrorCount;
}
break;
default:
break;
}
OldContext->Serialize(V, Verbosity, Category);
}
//------------------------------------------------------------------------------
void FBlueprintNativeCodeGenUtils::FScopedFeedbackContext::Flush()
{
WarningCount = ErrorCount = 0;
OldContext->Flush();
}