You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#add FClass and FClasses added to encapsulate (and ultimately replace) UClass and FClassTree. #add StringUtils contains a bunch of string-related functions which were in FHeaderParser for no real reason. #change Replaced FClassTree with UHT and fixed up all the code which was affected. #codereview robert.manuszewski [CL 2045258 by Steve Robb in Main branch]
4992 lines
178 KiB
C++
4992 lines
178 KiB
C++
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "UnrealHeaderTool.h"
|
|
|
|
#include "UniquePtr.h"
|
|
#include "ParserHelper.h"
|
|
#include "NativeClassExporter.h"
|
|
#include "HeaderParser.h"
|
|
#include "ClassMaps.h"
|
|
#include "Manifest.h"
|
|
|
|
/////////////////////////////////////////////////////
|
|
// Globals
|
|
|
|
FManifest GManifest;
|
|
|
|
static TArray<FString> ChangeMessages;
|
|
static bool bWriteContents = false;
|
|
static bool bVerifyContents = false;
|
|
|
|
static const bool bMultiLineUFUNCTION = true;
|
|
static const bool bMultiLineUPROPERTY = true;
|
|
|
|
static UClass* GenerateCodeForHeader(UObject* InParent, const TCHAR* Name, EObjectFlags Flags, const TCHAR* Buffer, int32& OutClassDeclLine);
|
|
|
|
FCompilerMetadataManager* GScriptHelper = NULL;
|
|
|
|
/** C++ name lookup helper */
|
|
FNameLookupCPP* NameLookupCPP;
|
|
|
|
/////////////////////////////////////////////////////
|
|
// FFlagAudit
|
|
|
|
//@todo: UCREMOVAL this is all audit stuff
|
|
|
|
static struct FFlagAudit
|
|
{
|
|
struct Pair
|
|
{
|
|
FString Name;
|
|
uint64 Flags;
|
|
Pair(UObject* Source, const TCHAR* FlagType, uint64 InFlags)
|
|
{
|
|
Name = Source->GetFullName() + TEXT("[") + FlagType + TEXT("]");
|
|
Flags = InFlags;
|
|
}
|
|
};
|
|
|
|
TArray<Pair> Items;
|
|
|
|
void Add(UObject* Source, const TCHAR* FlagType, uint64 Flags)
|
|
{
|
|
new (Items) Pair(Source, FlagType, Flags);
|
|
}
|
|
|
|
void WriteResults()
|
|
{
|
|
bool bDoDiff = false;
|
|
FString Filename;
|
|
FString RefFilename = FString(FPaths::GameSavedDir()) / TEXT("ReferenceFlags.txt");
|
|
if( !FParse::Param( FCommandLine::Get(), TEXT("WRITEFLAGS") ) )
|
|
{
|
|
return;
|
|
}
|
|
if( FParse::Param( FCommandLine::Get(), TEXT("WRITEREF") ) )
|
|
{
|
|
Filename = RefFilename;
|
|
}
|
|
else if( FParse::Param( FCommandLine::Get(), TEXT("VERIFYREF") ) )
|
|
{
|
|
Filename = FString(FPaths::GameSavedDir()) / TEXT("VerifyFlags.txt");
|
|
bDoDiff = true;
|
|
}
|
|
|
|
struct FComparePairByName
|
|
{
|
|
FORCEINLINE bool operator()( const FFlagAudit::Pair& A, const FFlagAudit::Pair& B ) const { return A.Name < B.Name; }
|
|
};
|
|
Items.Sort( FComparePairByName() );
|
|
|
|
int32 MaxLen = 0;
|
|
for (int32 Index = 0; Index < Items.Num(); Index++)
|
|
{
|
|
MaxLen = FMath::Max<int32>(Items[Index].Name.Len(), MaxLen);
|
|
}
|
|
MaxLen += 4;
|
|
FStringOutputDevice File;
|
|
for (int32 Index = 0; Index < Items.Num(); Index++)
|
|
{
|
|
File.Logf(TEXT("%s%s0x%016llx\r\n"), *Items[Index].Name, FCString::Spc(MaxLen - Items[Index].Name.Len()), Items[Index].Flags);
|
|
}
|
|
FFileHelper::SaveStringToFile(File, *Filename);
|
|
if (bDoDiff)
|
|
{
|
|
FString Verify = File;
|
|
FString Ref;
|
|
if (FFileHelper::LoadFileToString(Ref, *RefFilename))
|
|
{
|
|
FStringOutputDevice MisMatches;
|
|
TArray<FString> VerifyLines;
|
|
Verify.ParseIntoArray(&VerifyLines, TEXT("\n"), true);
|
|
TArray<FString> RefLines;
|
|
Ref.ParseIntoArray(&RefLines, TEXT("\n"), true);
|
|
check(VerifyLines.Num() == RefLines.Num()); // we aren't doing a sophisticated diff
|
|
for (int32 Index = 0; Index < RefLines.Num(); Index++)
|
|
{
|
|
if (RefLines[Index] != VerifyLines[Index])
|
|
{
|
|
MisMatches.Logf(TEXT("REF : %s"), *RefLines[Index]);
|
|
MisMatches.Logf(TEXT("VERIFY: %s"), *VerifyLines[Index]);
|
|
}
|
|
}
|
|
FString DiffFilename = FString(FPaths::GameSavedDir()) / TEXT("FlagsDiff.txt");
|
|
FFileHelper::SaveStringToFile(MisMatches, *DiffFilename);
|
|
}
|
|
}
|
|
}
|
|
} TheFlagAudit;
|
|
|
|
static FString Tabify(const TCHAR *Input)
|
|
{
|
|
FString Result(Input);
|
|
|
|
const int32 SpacesPerTab = 4;
|
|
Result.ReplaceInline(TEXT("\r\n"), TEXT("\n"));
|
|
|
|
TArray<FString> Lines;
|
|
Result.ParseIntoArray( &Lines, TEXT( "\n" ), false );
|
|
|
|
Result = TEXT("");
|
|
for (int32 Index = 0; Index < Lines.Num(); Index++)
|
|
{
|
|
if (Index)
|
|
{
|
|
Result += TEXT("\r\n");
|
|
}
|
|
FString& Line = Lines[Index];
|
|
int32 FirstNonWS = 0;
|
|
while (1)
|
|
{
|
|
TCHAR c = *(*Line + FirstNonWS);
|
|
if (c != TEXT('\t') && c != TEXT(' '))
|
|
{
|
|
break;
|
|
}
|
|
FirstNonWS++;
|
|
}
|
|
FString Start = Line.Left(FirstNonWS);
|
|
FString Remainder = *Line + FirstNonWS;
|
|
Start.ReplaceInline(TEXT("\t"), FCString::Spc(SpacesPerTab));
|
|
int32 NumTabs = Start.Len() / SpacesPerTab;
|
|
for (int32 Tab = 0; Tab < NumTabs; Tab++)
|
|
{
|
|
Result += TEXT("\t");
|
|
}
|
|
Result += FCString::Spc(Start.Len() - NumTabs * SpacesPerTab);
|
|
Result += Remainder;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Helper function for finding the location of a package
|
|
* This is required as source now lives in several possible directories
|
|
*
|
|
* @param InPackage The name of the package of interest
|
|
* @param OutLocation The location of the given package, if found
|
|
* @param OutHeaderLocation The directory where generated headers should be placed
|
|
*
|
|
* @return bool true if found, false if not
|
|
*/
|
|
bool MakeCommandlet_FindPackageLocation(const TCHAR* InPackage, FString& OutLocation, FString& OutHeaderLocation)
|
|
{
|
|
// Mapping of processed packages to their locations
|
|
// An empty location string means it was processed but not found
|
|
static TMap<FString, FManifest::FModule*> CheckedPackageList;
|
|
|
|
FString CheckPackage(InPackage);
|
|
|
|
auto* ModuleInfoPtr = CheckedPackageList.FindRef(CheckPackage);
|
|
|
|
if (!ModuleInfoPtr)
|
|
{
|
|
auto* ModuleInfoPtr2 = GManifest.Modules.FindByPredicate([&](FManifest::FModule& Module) { return Module.Name == CheckPackage; });
|
|
if (ModuleInfoPtr2 && IFileManager::Get().DirectoryExists(*ModuleInfoPtr2->BaseDirectory))
|
|
{
|
|
ModuleInfoPtr = ModuleInfoPtr2;
|
|
CheckedPackageList.Add(CheckPackage, ModuleInfoPtr);
|
|
}
|
|
}
|
|
|
|
if (!ModuleInfoPtr)
|
|
return false;
|
|
|
|
OutLocation = ModuleInfoPtr->BaseDirectory;
|
|
OutHeaderLocation = ModuleInfoPtr->GeneratedIncludeDirectory;
|
|
return true;
|
|
}
|
|
|
|
|
|
FString Macroize(const TCHAR *MacroName, const TCHAR *StringToMacroize)
|
|
{
|
|
FString Result = StringToMacroize;
|
|
if (Result.Len())
|
|
{
|
|
Result.ReplaceInline(TEXT("\r\n"), TEXT("\n"));
|
|
Result.ReplaceInline(TEXT("\n"), TEXT(" \\\n"));
|
|
check(Result.EndsWith(TEXT(" \\\n")));
|
|
Result = Result.LeftChop(3);
|
|
Result += TEXT("\n\n\n");
|
|
Result.ReplaceInline(TEXT("\n"), TEXT("\r\n"));
|
|
}
|
|
return FString::Printf(TEXT("#define %s%s\r\n"), MacroName, Result.Len() ? TEXT(" \\") : TEXT("")) + Result;
|
|
}
|
|
|
|
struct FParmsAndReturnProperties
|
|
{
|
|
FParmsAndReturnProperties()
|
|
: Return(NULL)
|
|
{
|
|
}
|
|
|
|
#if PLATFORM_COMPILER_HAS_DEFAULTED_FUNCTIONS
|
|
|
|
FParmsAndReturnProperties(FParmsAndReturnProperties&&) = default;
|
|
FParmsAndReturnProperties(const FParmsAndReturnProperties&) = default;
|
|
FParmsAndReturnProperties& operator=(FParmsAndReturnProperties&&) = default;
|
|
FParmsAndReturnProperties& operator=(const FParmsAndReturnProperties&) = default;
|
|
|
|
#else
|
|
|
|
FParmsAndReturnProperties( FParmsAndReturnProperties&& Other) : Parms(MoveTemp(Other.Parms)), Return(MoveTemp(Other.Return)) {}
|
|
FParmsAndReturnProperties(const FParmsAndReturnProperties& Other) : Parms( Other.Parms ), Return( Other.Return ) {}
|
|
FParmsAndReturnProperties& operator=( FParmsAndReturnProperties&& Other) { Parms = MoveTemp(Other.Parms); Return = MoveTemp(Other.Return); return *this; }
|
|
FParmsAndReturnProperties& operator=(const FParmsAndReturnProperties& Other) { Parms = Other.Parms ; Return = Other.Return ; return *this; }
|
|
|
|
#endif
|
|
|
|
bool HasParms() const
|
|
{
|
|
return Parms.Num() || Return;
|
|
}
|
|
|
|
TArray<UProperty*> Parms;
|
|
UProperty* Return;
|
|
};
|
|
|
|
/**
|
|
* Get parameters and return type for a given function.
|
|
*
|
|
* @param Function The function to get the parameters for.
|
|
* @return An aggregate containing the parameters and return type of that function.
|
|
*/
|
|
FParmsAndReturnProperties GetFunctionParmsAndReturn(UFunction* Function)
|
|
{
|
|
FParmsAndReturnProperties Result;
|
|
for ( TFieldIterator<UProperty> It(Function); It; ++It)
|
|
{
|
|
UProperty* Field = *It;
|
|
|
|
if ((It->PropertyFlags & (CPF_Parm | CPF_ReturnParm)) == CPF_Parm)
|
|
{
|
|
Result.Parms.Add(Field);
|
|
}
|
|
else if (It->PropertyFlags & CPF_ReturnParm)
|
|
{
|
|
Result.Return = Field;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Determines whether the glue version of the specified native function
|
|
* should be exported
|
|
*
|
|
* @param Function the function to check
|
|
* @return true if the glue version of the function should be exported.
|
|
*/
|
|
bool FNativeClassHeaderGenerator::ShouldExportFunction( UFunction* Function )
|
|
{
|
|
// export any script stubs for native functions declared in interface classes
|
|
bool bIsBlueprintNativeEvent = (Function->FunctionFlags & FUNC_BlueprintEvent) && (Function->FunctionFlags & FUNC_Native);
|
|
if (Function->GetOwnerClass()->HasAnyClassFlags(CLASS_Interface) && !bIsBlueprintNativeEvent)
|
|
return true;
|
|
|
|
// always export if the function is static
|
|
if (Function->FunctionFlags & FUNC_Static)
|
|
return true;
|
|
|
|
// don't export the function if this is not the original declaration and there is
|
|
// at least one parent version of the function that is declared native
|
|
for (UFunction* ParentFunction = Function->GetSuperFunction(); ParentFunction; ParentFunction = ParentFunction->GetSuperFunction())
|
|
{
|
|
if (ParentFunction->FunctionFlags & FUNC_Native)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
FString CreateLiteralString(const FString& Str)
|
|
{
|
|
FString Result = TEXT("TEXT(\"");
|
|
|
|
// Have a reasonable guess at reserving the right size
|
|
Result.Reserve(Str.Len() + Result.Len());
|
|
|
|
bool bPreviousCharacterWasHex = false;
|
|
|
|
const TCHAR* Ptr = *Str;
|
|
while (TCHAR Ch = *Ptr++)
|
|
{
|
|
switch (Ch)
|
|
{
|
|
case TEXT('\r'): continue;
|
|
case TEXT('\n'): Result += TEXT("\\n"); bPreviousCharacterWasHex = false; break;
|
|
case TEXT('\\'): Result += TEXT("\\\\"); bPreviousCharacterWasHex = false; break;
|
|
case TEXT('\"'): Result += TEXT("\\\""); bPreviousCharacterWasHex = false; break;
|
|
default:
|
|
if (Ch < 31 || Ch >= 128)
|
|
{
|
|
Result += FString::Printf(TEXT("\\x%04x"), Ch);
|
|
bPreviousCharacterWasHex = true;
|
|
}
|
|
else
|
|
{
|
|
// We close and open the literal (with TEXT) here in order to ensure that successive hex characters aren't appended to the hex sequence, causing a different number
|
|
if (bPreviousCharacterWasHex && FCharWide::IsHexDigit(Ch))
|
|
{
|
|
Result += "\")TEXT(\"";
|
|
}
|
|
bPreviousCharacterWasHex = false;
|
|
Result += Ch;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
Result += TEXT("\")");
|
|
return Result;
|
|
}
|
|
|
|
static FString GetMetaDataCodeForObject(UObject* Object, const TCHAR* SymbolName, const TCHAR* Spaces)
|
|
{
|
|
TMap<FName, FString>* MetaData = UMetaData::GetMapForObject(Object);
|
|
|
|
FString Result;
|
|
if (MetaData && MetaData->Num())
|
|
{
|
|
typedef TKeyValuePair<FName, FString> KVPType;
|
|
TArray<KVPType> KVPs;
|
|
for (auto& KVP : *MetaData)
|
|
{
|
|
KVPs.Add(KVPType(KVP.Key, KVP.Value));
|
|
}
|
|
|
|
// We sort the metadata here so that we can get consistent output across multiple runs
|
|
// even when metadata is added in a different order
|
|
KVPs.Sort([](const KVPType& Lhs, const KVPType& Rhs) { return Lhs.Key < Rhs.Key; });
|
|
|
|
for (const auto& KVP : KVPs)
|
|
{
|
|
Result += FString::Printf(TEXT("%sMetaData->SetValue(%s, TEXT(\"%s\"), %s);\r\n"), Spaces, SymbolName, *KVP.Key.ToString(), *CreateLiteralString(KVP.Value));
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Returns a C++ code string for declaring a class or variable as an export, if the class is configured for that
|
|
*
|
|
* @return The API declaration string
|
|
*/
|
|
FString FNativeClassHeaderGenerator::MakeAPIDecl() const
|
|
{
|
|
FString APIDecl;
|
|
if( CurrentClass->HasAnyClassFlags( CLASS_RequiredAPI ) )
|
|
{
|
|
APIDecl = FString::Printf( TEXT( "%s_API " ), *API );
|
|
}
|
|
|
|
return APIDecl;
|
|
}
|
|
|
|
/**
|
|
* Exports the struct's C++ properties to the specified output device and adds special
|
|
* compiler directives for GCC to pack as we expect.
|
|
*
|
|
* @param Struct UStruct to export properties
|
|
* @param TextIndent Current text indentation
|
|
* @param ImportsDefaults whether this struct will be serialized with a default value
|
|
*/
|
|
void FNativeClassHeaderGenerator::ExportProperties( UStruct* Struct, int32 TextIndent, bool bAccessSpecifiers, class FStringOutputDevice* Output )
|
|
{
|
|
UProperty* Previous = NULL;
|
|
UProperty* PreviousNonEditorOnly = NULL;
|
|
UProperty* LastInSuper = NULL;
|
|
UStruct* InheritanceSuper = Struct->GetInheritanceSuper();
|
|
bool bEmittedHasEditorOnlyMacro = false;
|
|
bool bEmittedHasScriptAlign = false;
|
|
|
|
check(Output != NULL)
|
|
FStringOutputDevice& HeaderOutput = *Output;
|
|
|
|
|
|
// Find last property in the lowest base class that has any properties
|
|
UStruct* CurrentSuper = InheritanceSuper;
|
|
while (LastInSuper == NULL && CurrentSuper)
|
|
{
|
|
for( TFieldIterator<UProperty> It(CurrentSuper,EFieldIteratorFlags::ExcludeSuper); It; ++It )
|
|
{
|
|
UProperty* Current = *It;
|
|
|
|
// Disregard properties with 0 size like functions.
|
|
if( It.GetStruct() == CurrentSuper && Current->ElementSize )
|
|
{
|
|
LastInSuper = Current;
|
|
}
|
|
}
|
|
// go up a layer in the hierarchy
|
|
CurrentSuper = CurrentSuper->GetSuperStruct();
|
|
}
|
|
|
|
EPropertyHeaderExportFlags CurrentExportType = PROPEXPORT_Public;
|
|
|
|
// find structs that are nothing but bytes, account for editor only properties being
|
|
// removed on consoles
|
|
int32 NumProperties = 0;
|
|
int32 NumByteProperties = 0;
|
|
int32 NumNonEditorOnlyProperties = 0;
|
|
int32 NumNonEditorOnlyByteProperties = 0;
|
|
for( TFieldIterator<UProperty> It(Struct, EFieldIteratorFlags::ExcludeSuper); It; ++It )
|
|
{
|
|
// treat bitfield and bytes the same
|
|
bool bIsByteProperty = It->IsA(UByteProperty::StaticClass());// || It->IsA(UBoolProperty::StaticClass());
|
|
bool bIsEditorOnlyProperty = It->IsEditorOnlyProperty();
|
|
|
|
// count our propertie
|
|
NumProperties++;
|
|
if (bIsByteProperty)
|
|
{
|
|
NumByteProperties++;
|
|
}
|
|
if (!bIsEditorOnlyProperty)
|
|
{
|
|
NumNonEditorOnlyProperties++;
|
|
}
|
|
if (!bIsEditorOnlyProperty && bIsByteProperty)
|
|
{
|
|
NumNonEditorOnlyByteProperties++;
|
|
}
|
|
}
|
|
|
|
bool bCurrentlyInNotCPPBlock = false;
|
|
// Iterate over all properties in this struct.
|
|
for( TFieldIterator<UProperty> It(Struct, EFieldIteratorFlags::ExcludeSuper); It; ++It )
|
|
{
|
|
UProperty* Current = *It;
|
|
|
|
FStringOutputDevice PropertyText;
|
|
|
|
// Disregard properties with 0 size like functions.
|
|
if (It.GetStruct() == Struct)
|
|
{
|
|
FString AccessSpecifier = TEXT("public");
|
|
if (bAccessSpecifiers)
|
|
{
|
|
// find the class info for this class
|
|
FClassMetaData* ClassData = GScriptHelper->FindClassData(CurrentClass);
|
|
|
|
// find the compiler token for this property
|
|
FTokenData* PropData = ClassData->FindTokenData(Current);
|
|
if ( PropData != NULL )
|
|
{
|
|
// if this property has a different access specifier, then export that now
|
|
if ( (PropData->Token.PropertyExportFlags & CurrentExportType) == 0 )
|
|
{
|
|
if ( (PropData->Token.PropertyExportFlags & PROPEXPORT_Private) != 0 )
|
|
{
|
|
CurrentExportType = PROPEXPORT_Private;
|
|
AccessSpecifier = TEXT("private");
|
|
}
|
|
else if ( (PropData->Token.PropertyExportFlags & PROPEXPORT_Protected) != 0 )
|
|
{
|
|
CurrentExportType = PROPEXPORT_Protected;
|
|
AccessSpecifier = TEXT("protected");
|
|
}
|
|
else
|
|
{
|
|
CurrentExportType = PROPEXPORT_Public;
|
|
AccessSpecifier = TEXT("public");
|
|
}
|
|
|
|
if ( AccessSpecifier.Len() )
|
|
{
|
|
// If we are changing the access specifier we need to emit the #endif for the WITH_EDITORONLY_DATA macro first otherwise the access specifier may
|
|
// only be conditionally compiled in.
|
|
if( bEmittedHasEditorOnlyMacro )
|
|
{
|
|
PropertyText.Logf( TEXT("#endif // WITH_EDITORONLY_DATA\r\n") );
|
|
bEmittedHasEditorOnlyMacro = false;
|
|
}
|
|
|
|
PropertyText.Logf(TEXT("%s:%s"), *AccessSpecifier, LINE_TERMINATOR);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// If we are switching from editor to non-editor or vice versa and the state of the WITH_EDITORONLY_DATA macro emission doesn't match, generate the
|
|
// #if or #endif appropriately.
|
|
bool RequiresHasEditorOnlyMacro = Current->IsEditorOnlyProperty();
|
|
if( !bEmittedHasEditorOnlyMacro && RequiresHasEditorOnlyMacro )
|
|
{
|
|
// Indent code and export CPP text.
|
|
PropertyText.Logf( TEXT("#if WITH_EDITORONLY_DATA\r\n") );
|
|
bEmittedHasEditorOnlyMacro = true;
|
|
}
|
|
else if( bEmittedHasEditorOnlyMacro && !RequiresHasEditorOnlyMacro )
|
|
{
|
|
PropertyText.Logf( TEXT("#endif // WITH_EDITORONLY_DATA\r\n") );
|
|
bEmittedHasEditorOnlyMacro = false;
|
|
}
|
|
|
|
// Export property specifiers
|
|
// Indent code and export CPP text.
|
|
{
|
|
FStringOutputDevice JustPropertyDecl;
|
|
JustPropertyDecl.Log( FCString::Spc(TextIndent+4) );
|
|
|
|
const FString* Dim = GArrayDimensions.Find(Current);
|
|
Current->ExportCppDeclaration( JustPropertyDecl, EExportedDeclaration::Member, Dim ? **Dim : NULL);
|
|
ApplyAlternatePropertyExportText(*It, JustPropertyDecl);
|
|
|
|
// Finish up line.
|
|
JustPropertyDecl.Log(TEXT(";"));
|
|
|
|
// Finish up line.
|
|
PropertyText.Logf(TEXT("%s\r\n"), *JustPropertyDecl);
|
|
}
|
|
|
|
LastInSuper = NULL;
|
|
Previous = Current;
|
|
if (!Current->IsEditorOnlyProperty())
|
|
{
|
|
PreviousNonEditorOnly = Current;
|
|
}
|
|
|
|
HeaderOutput.Log(PropertyText);
|
|
}
|
|
}
|
|
if (bCurrentlyInNotCPPBlock)
|
|
{
|
|
HeaderOutput.Log(TEXT("#endif\r\n"));
|
|
bCurrentlyInNotCPPBlock = false;
|
|
}
|
|
|
|
// End of property list. If we haven't generated the WITH_EDITORONLY_DATA #endif, do so now.
|
|
if (bEmittedHasEditorOnlyMacro)
|
|
{
|
|
HeaderOutput.Logf( TEXT("#endif // WITH_EDITORONLY_DATA\r\n") );
|
|
}
|
|
|
|
// if the last property that was exported wasn't public, emit a line to reset the access to "public" so that we don't interfere with cpptext
|
|
if ( CurrentExportType != PROPEXPORT_Public )
|
|
{
|
|
HeaderOutput.Logf(TEXT("public:%s"), LINE_TERMINATOR);
|
|
}
|
|
}
|
|
|
|
/** This table maps a singleton name to an extern declaration for it. For cross-module access, we add these to GeneratedFunctionDeclarations **/
|
|
static TMap<FString, FString> SingletonNameToExternDecl;
|
|
|
|
FString FNativeClassHeaderGenerator::GetSingletonName(UField* Item, bool bRequiresValidObject)
|
|
{
|
|
UClass* ItemClass = Cast<UClass>(Item);
|
|
if (ItemClass && ItemClass->HasAllClassFlags(CLASS_Intrinsic))
|
|
{
|
|
return FString::Printf(TEXT("%s::StaticClass()"), NameLookupCPP->GetNameCPP(ItemClass));
|
|
}
|
|
|
|
FString Suffix;
|
|
if (ItemClass && !bRequiresValidObject && ItemClass->HasAllClassFlags(CLASS_Native))
|
|
{
|
|
Suffix = TEXT("_NoRegister");
|
|
}
|
|
|
|
FString Result;
|
|
UObject* Outer = Item;
|
|
while (Outer)
|
|
{
|
|
if (Cast<UClass>(Outer) || Cast<UScriptStruct>(Outer))
|
|
{
|
|
FString OuterName = NameLookupCPP->GetNameCPP(Cast<UStruct>(Outer));
|
|
if (Result.Len())
|
|
{
|
|
Result = OuterName + TEXT("_") + Result;
|
|
}
|
|
else
|
|
{
|
|
Result = OuterName;
|
|
}
|
|
// Structs can also have UPackage outer.
|
|
if (Cast<UClass>(Outer) || Cast<UPackage>(Outer->GetOuter()))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else if (Result.Len())
|
|
{
|
|
// Handle UEnums with UPackage outer.
|
|
Result = Outer->GetName() + TEXT("_") + Result;
|
|
}
|
|
else
|
|
{
|
|
Result = Outer->GetName();
|
|
}
|
|
Outer = Outer->GetOuter();
|
|
}
|
|
FString ClassString = NameLookupCPP->GetNameCPP(Item->GetClass());
|
|
// Can't use long package names in function names.
|
|
if (Result.StartsWith(TEXT("/Script/")))
|
|
{
|
|
Result = FPackageName::GetShortName(Result);
|
|
}
|
|
Result = FString(TEXT("Z_Construct_")) + ClassString + TEXT("_") + Result + Suffix + TEXT("()");
|
|
if (CastChecked<UPackage>(Item->GetOutermost()) != Package)
|
|
{
|
|
// this is a cross module reference, we need to include the right extern decl
|
|
FString* Extern = SingletonNameToExternDecl.Find(Result);
|
|
if (!Extern)
|
|
{
|
|
UE_LOG(LogCompile, Warning, TEXT("Could not find extern declaration for cross module reference %s\n"), *Result);
|
|
}
|
|
else
|
|
{
|
|
bool bAlreadyInSet = false;
|
|
UniqueCrossModuleReferences.Add(*Extern, &bAlreadyInSet);
|
|
|
|
if (!bAlreadyInSet)
|
|
{
|
|
CrossModuleGeneratedFunctionDeclarations.Log(*Extern);
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
FString FNativeClassHeaderGenerator::PropertyNew(FString& Meta, UProperty* Prop, const FString& OuterString, const FString& PropMacro, const TCHAR* NameSuffix, const TCHAR* Spaces, const TCHAR* SourceStruct)
|
|
{
|
|
FString ExtraArgs;
|
|
|
|
FString PropNameDep = Prop->GetName();
|
|
if (Prop->HasAllPropertyFlags(CPF_Deprecated))
|
|
{
|
|
PropNameDep += TEXT("_DEPRECATED");
|
|
}
|
|
|
|
if (UObjectPropertyBase* ObjectProperty = Cast<UObjectPropertyBase>(Prop))
|
|
{
|
|
UClass *TargetClass = ObjectProperty->PropertyClass;
|
|
if (UClassProperty* ClassProperty = Cast<UClassProperty>(Prop))
|
|
{
|
|
TargetClass = ClassProperty->MetaClass;
|
|
}
|
|
if (UAssetClassProperty* SubclassOfProperty = Cast<UAssetClassProperty>(Prop))
|
|
{
|
|
TargetClass = SubclassOfProperty->MetaClass;
|
|
}
|
|
ExtraArgs = FString::Printf(TEXT(", %s"), *GetSingletonName(TargetClass, false));
|
|
}
|
|
else if (UInterfaceProperty* InterfaceProperty = Cast<UInterfaceProperty>(Prop))
|
|
{
|
|
UClass *TargetClass = InterfaceProperty->InterfaceClass;
|
|
ExtraArgs = FString::Printf(TEXT(", %s"), *GetSingletonName(TargetClass, false));
|
|
}
|
|
else if (UStructProperty* StructProperty = Cast<UStructProperty>(Prop))
|
|
{
|
|
UScriptStruct* Struct = StructProperty->Struct;
|
|
check(Struct);
|
|
ExtraArgs = FString::Printf(TEXT(", %s"), *GetSingletonName(Struct));
|
|
}
|
|
else if (UByteProperty* ByteProperty = Cast<UByteProperty>(Prop))
|
|
{
|
|
if (ByteProperty->Enum)
|
|
{
|
|
ExtraArgs = FString::Printf(TEXT(", %s"), *GetSingletonName(ByteProperty->Enum));
|
|
}
|
|
}
|
|
else if (UBoolProperty* BoolProperty = Cast<UBoolProperty>(Prop))
|
|
{
|
|
if (Cast<UArrayProperty>(BoolProperty->GetOuter()))
|
|
{
|
|
ExtraArgs = FString(TEXT(", 0")); // this is an array of C++ bools so the mask is irrelevant.
|
|
}
|
|
else
|
|
{
|
|
check(SourceStruct);
|
|
ExtraArgs = FString::Printf(TEXT(", CPP_BOOL_PROPERTY_BITMASK(%s, %s)"), *PropNameDep, SourceStruct);
|
|
}
|
|
ExtraArgs += FString::Printf(TEXT(", sizeof(%s), %s"), *BoolProperty->GetCPPType(NULL, 0), BoolProperty->IsNativeBool() ? TEXT("true") : TEXT("false"));
|
|
}
|
|
else if (UDelegateProperty* DelegateProperty = Cast<UDelegateProperty>(Prop))
|
|
{
|
|
UFunction *TargetFunction = DelegateProperty->SignatureFunction;
|
|
ExtraArgs = FString::Printf(TEXT(", %s"), *GetSingletonName(TargetFunction));
|
|
}
|
|
else if (UMulticastDelegateProperty* MulticastDelegateProperty = Cast<UMulticastDelegateProperty>(Prop))
|
|
{
|
|
UFunction *TargetFunction = MulticastDelegateProperty->SignatureFunction;
|
|
ExtraArgs = FString::Printf(TEXT(", %s"), *GetSingletonName(TargetFunction));
|
|
}
|
|
|
|
FString Constructor = FString::Printf(TEXT("new(%s, TEXT(\"%s\"), RF_Public|RF_Transient|RF_Native) U%s(%s, 0x%016llx%s);"),
|
|
*OuterString,
|
|
*Prop->GetName(),
|
|
*Prop->GetClass()->GetName(),
|
|
*PropMacro,
|
|
Prop->PropertyFlags & ~CPF_ComputedFlags,
|
|
*ExtraArgs);
|
|
TheFlagAudit.Add(Prop, TEXT("PropertyFlags"), Prop->PropertyFlags);
|
|
|
|
FString Symbol = FString::Printf(TEXT("NewProp_%s%s"), *Prop->GetName(), NameSuffix);
|
|
FString Lines = FString::Printf(TEXT("%sUProperty* %s = %s\r\n"),
|
|
Spaces,
|
|
*Symbol,
|
|
*Constructor);
|
|
|
|
if (Prop->ArrayDim != 1)
|
|
{
|
|
Lines += FString::Printf(TEXT("%s%s->ArrayDim = CPP_ARRAY_DIM(%s, %s);\r\n"), Spaces, *Symbol, *PropNameDep, SourceStruct);
|
|
}
|
|
|
|
if (Prop->RepNotifyFunc != NAME_None)
|
|
{
|
|
Lines += FString::Printf(TEXT("%s%s->RepNotifyFunc = FName(TEXT(\"%s\"));\r\n"), Spaces, *Symbol, *Prop->RepNotifyFunc.ToString());
|
|
}
|
|
Meta += GetMetaDataCodeForObject(Prop, *Symbol, Spaces);
|
|
return Lines;
|
|
}
|
|
|
|
void FNativeClassHeaderGenerator::OutputProperties(FString& Meta, FOutputDevice& OutputDevice, const FString& OuterString, TArray<UProperty*>& Properties, const TCHAR* Spaces)
|
|
{
|
|
bool bEmittedHasEditorOnlyMacro = false;
|
|
for (int32 Index = Properties.Num() - 1; Index >= 0; Index--)
|
|
{
|
|
bool RequiresHasEditorOnlyMacro = Properties[Index]->IsEditorOnlyProperty();
|
|
if (!bEmittedHasEditorOnlyMacro && RequiresHasEditorOnlyMacro)
|
|
{
|
|
// Indent code and export CPP text.
|
|
OutputDevice.Logf( TEXT("#if WITH_EDITORONLY_DATA\r\n") );
|
|
bEmittedHasEditorOnlyMacro = true;
|
|
}
|
|
else if (bEmittedHasEditorOnlyMacro && !RequiresHasEditorOnlyMacro)
|
|
{
|
|
OutputDevice.Logf( TEXT("#endif // WITH_EDITORONLY_DATA\r\n") );
|
|
bEmittedHasEditorOnlyMacro = false;
|
|
}
|
|
OutputProperty(Meta, OutputDevice, OuterString, Properties[Index], TEXT(" "));
|
|
}
|
|
if (bEmittedHasEditorOnlyMacro)
|
|
{
|
|
OutputDevice.Logf( TEXT("#endif // WITH_EDITORONLY_DATA\r\n") );
|
|
}
|
|
}
|
|
|
|
inline FString GetEventStructParamsName(const TCHAR* ClassName, const TCHAR* FunctionName)
|
|
{
|
|
FString Result = FString::Printf(TEXT("%s_event%s_Parms"), ClassName, FunctionName);
|
|
return Result;
|
|
}
|
|
|
|
void FNativeClassHeaderGenerator::OutputProperty(FString& Meta, FOutputDevice& OutputDevice, const FString& OuterString, UProperty* Prop, const TCHAR* Spaces)
|
|
{
|
|
{
|
|
FString SourceStruct;
|
|
UFunction* Function = Cast<UFunction>(Prop->GetOuter());
|
|
if (Function)
|
|
{
|
|
while (Function->GetSuperFunction())
|
|
{
|
|
Function = Function->GetSuperFunction();
|
|
}
|
|
FString FunctionName = Function->GetName();
|
|
if( Function->HasAnyFunctionFlags( FUNC_Delegate ) )
|
|
{
|
|
FunctionName = FunctionName.LeftChop( FString( HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX ).Len() );
|
|
}
|
|
|
|
SourceStruct = GetEventStructParamsName(*CastChecked<UClass>(Function->GetOuter())->GetName(), *FunctionName);
|
|
}
|
|
else
|
|
{
|
|
SourceStruct = NameLookupCPP->GetNameCPP(CastChecked<UStruct>(Prop->GetOuter()));
|
|
}
|
|
FString PropMacroOuterClass;
|
|
FString PropNameDep = Prop->GetName();
|
|
if (Prop->HasAllPropertyFlags(CPF_Deprecated))
|
|
{
|
|
PropNameDep += TEXT("_DEPRECATED");
|
|
}
|
|
UBoolProperty* BoolProperty = Cast<UBoolProperty>(Prop);
|
|
if (BoolProperty)
|
|
{
|
|
OutputDevice.Logf(TEXT("%sCPP_BOOL_PROPERTY_BITMASK_STRUCT(%s, %s, %s);\r\n"),
|
|
Spaces,
|
|
*PropNameDep,
|
|
*SourceStruct,
|
|
*BoolProperty->GetCPPType(NULL, 0));
|
|
PropMacroOuterClass = FString::Printf(TEXT("FPostConstructInitializeProperties(), EC_CppProperty, CPP_BOOL_PROPERTY_OFFSET(%s, %s)"),
|
|
*PropNameDep, *SourceStruct);
|
|
}
|
|
else
|
|
{
|
|
PropMacroOuterClass = FString::Printf(TEXT("CPP_PROPERTY_BASE(%s, %s)"), *PropNameDep, *SourceStruct);
|
|
}
|
|
OutputDevice.Log(*PropertyNew(Meta, Prop, OuterString, PropMacroOuterClass, TEXT(""), Spaces, *SourceStruct));
|
|
}
|
|
UArrayProperty* ArrayProperty = Cast<UArrayProperty>(Prop);
|
|
if (ArrayProperty)
|
|
{
|
|
FString InnerOuterString = FString::Printf(TEXT("NewProp_%s"), *Prop->GetName());
|
|
FString PropMacroOuterArray = TEXT("FPostConstructInitializeProperties(), EC_CppProperty, 0");
|
|
OutputDevice.Log(*PropertyNew(Meta, ArrayProperty->Inner, InnerOuterString, PropMacroOuterArray, TEXT("_Inner"), Spaces));
|
|
}
|
|
}
|
|
|
|
static FString BackSlashAndIndent(const FString& Text)
|
|
{
|
|
FString Trailer(TEXT(" \\\r\n "));
|
|
FString Result = FString(TEXT(" ")) + Text.Replace(TEXT("\r\n"), *Trailer);
|
|
return Result.LeftChop(Trailer.Len()) + TEXT("\r\n");
|
|
}
|
|
|
|
static bool IsAlwaysAccessible(UScriptStruct* Script)
|
|
{
|
|
FName ToTest = Script->GetFName();
|
|
if (ToTest == NAME_Matrix)
|
|
{
|
|
return false; // special case, the C++ FMatrix does not have the same members.
|
|
}
|
|
bool Result = Script->HasDefaults(); // if we have cpp struct ops in it for UHT, then we can assume it is always accessible
|
|
if( ToTest == NAME_Plane
|
|
|| ToTest == NAME_Vector
|
|
|| ToTest == NAME_Vector4
|
|
|| ToTest == NAME_Quat
|
|
|| ToTest == NAME_Color
|
|
)
|
|
{
|
|
check(Result);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
static void FindNoExportStructs(TArray<UScriptStruct*>& Structs, UStruct* Start)
|
|
{
|
|
while (Start)
|
|
{
|
|
if (UScriptStruct* StartScript = Cast<UScriptStruct>(Start))
|
|
{
|
|
if (StartScript->StructFlags & STRUCT_Native)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (!IsAlwaysAccessible(StartScript)) // these are a special cases that already exists and if wrong if exported nievely
|
|
{
|
|
// this will topologically sort them in reverse order
|
|
Structs.Remove(StartScript);
|
|
Structs.Add(StartScript);
|
|
}
|
|
}
|
|
|
|
for ( TFieldIterator<UProperty> ItInner(Start, EFieldIteratorFlags::ExcludeSuper); ItInner; ++ItInner )
|
|
{
|
|
UStructProperty* InnerStruct = Cast<UStructProperty>(*ItInner);
|
|
UArrayProperty* InnerArray = Cast<UArrayProperty>(*ItInner);
|
|
if (InnerArray)
|
|
{
|
|
InnerStruct = Cast<UStructProperty>(InnerArray->Inner);
|
|
}
|
|
if (InnerStruct)
|
|
{
|
|
FindNoExportStructs(Structs, InnerStruct->Struct);
|
|
}
|
|
}
|
|
Start = Start->GetSuperStruct();
|
|
}
|
|
}
|
|
|
|
FString GetPackageSingletonName(UPackage* Package)
|
|
{
|
|
static FString ClassString = NameLookupCPP->GetNameCPP(UPackage::StaticClass());
|
|
return FString(TEXT("Z_Construct_")) + ClassString + TEXT("_") + FPackageName::GetShortName(Package) + TEXT("()");
|
|
}
|
|
|
|
void FNativeClassHeaderGenerator::ExportGeneratedPackageInitCode(UPackage* Package)
|
|
{
|
|
FString ApiString = FString::Printf(TEXT("%s_API "), *API);
|
|
FString SingletonName(GetPackageSingletonName(Package));
|
|
|
|
if( GeneratedFunctionBodyTextSplit.Num() == 0 )
|
|
{
|
|
new(GeneratedFunctionBodyTextSplit) FStringOutputDeviceCountLines;
|
|
}
|
|
FStringOutputDevice& GeneratedFunctionText = GeneratedFunctionBodyTextSplit[GeneratedFunctionBodyTextSplit.Num()-1];
|
|
|
|
GeneratedFunctionDeclarations.Logf(TEXT(" %sclass UPackage* %s;\r\n"), *ApiString, *SingletonName);
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" UPackage* %s\r\n"), *SingletonName);
|
|
GeneratedFunctionText.Logf(TEXT(" {\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" static UPackage* ReturnPackage = NULL;\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" if (!ReturnPackage)\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" {\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" ReturnPackage = CastChecked<UPackage>(StaticFindObjectFast(UPackage::StaticClass(), NULL, FName(TEXT(\"%s\")), false, false));\r\n"), *Package->GetName());
|
|
|
|
FString Meta = GetMetaDataCodeForObject(Package, TEXT("ReturnPackage"), TEXT(" "));
|
|
if (Meta.Len())
|
|
{
|
|
GeneratedFunctionText.Logf(TEXT("#if WITH_METADATA\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" UMetaData* MetaData = ReturnPackage->GetMetaData();\r\n"));
|
|
GeneratedFunctionText.Log(*Meta);
|
|
GeneratedFunctionText.Logf(TEXT("#endif\r\n"));
|
|
}
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" ReturnPackage->PackageFlags |= PKG_CompiledIn | 0x%08X;\r\n"), Package->PackageFlags & (PKG_ClientOptional|PKG_ServerSideOnly));
|
|
TheFlagAudit.Add(Package, TEXT("PackageFlags"), Package->PackageFlags);
|
|
{
|
|
FGuid Guid;
|
|
Guid.A = FCrc::StrCrc_DEPRECATED(*GeneratedFunctionText .ToUpper());
|
|
Guid.B = FCrc::StrCrc_DEPRECATED(*GeneratedFunctionDeclarations.ToUpper());
|
|
GeneratedFunctionText.Logf(TEXT(" FGuid Guid;\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" Guid.A = 0x%08X;\r\n"), Guid.A);
|
|
GeneratedFunctionText.Logf(TEXT(" Guid.B = 0x%08X;\r\n"), Guid.B);
|
|
GeneratedFunctionText.Logf(TEXT(" Guid.C = 0x%08X;\r\n"), Guid.C);
|
|
GeneratedFunctionText.Logf(TEXT(" Guid.D = 0x%08X;\r\n"), Guid.D);
|
|
GeneratedFunctionText.Logf(TEXT(" ReturnPackage->SetGuid(Guid);\r\n"), Guid.D);
|
|
}
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" }\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" return ReturnPackage;\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" }\r\n"));
|
|
|
|
}
|
|
|
|
void FNativeClassHeaderGenerator::ExportNativeGeneratedInitCode(UClass* Class)
|
|
{
|
|
// Emit the IMPLEMENT_CLASS macro to go in the generated cpp file (unless it's the temporary UHT class).
|
|
if (!Class->HasAnyClassFlags(CLASS_Temporary))
|
|
{
|
|
GeneratedPackageCPP.Logf(TEXT(" IMPLEMENT_CLASS(%s);\r\n"),NameLookupCPP->GetNameCPP(Class));
|
|
}
|
|
|
|
int32 MaxLinesPerCpp = 30000;
|
|
|
|
if ((GeneratedFunctionBodyTextSplit.Num() == 0) || (GeneratedFunctionBodyTextSplit[GeneratedFunctionBodyTextSplit.Num()-1].GetLineCount() > MaxLinesPerCpp))
|
|
{
|
|
new(GeneratedFunctionBodyTextSplit) FStringOutputDeviceCountLines;
|
|
}
|
|
FStringOutputDevice& GeneratedFunctionText = GeneratedFunctionBodyTextSplit[GeneratedFunctionBodyTextSplit.Num()-1];
|
|
|
|
check(!FriendText.Len());
|
|
// Emit code to build the UObjects that used to be in .u files
|
|
bool bIsNoExport = Class->HasAnyClassFlags(CLASS_NoExport|CLASS_Temporary);
|
|
FStringOutputDevice BodyText;
|
|
FStringOutputDevice CallSingletons;
|
|
FString ApiString = FString::Printf(TEXT("%s_API "), *API);
|
|
|
|
// enums
|
|
for ( TFieldIterator<UEnum> It(Class, EFieldIteratorFlags::ExcludeSuper); It; ++It )
|
|
{
|
|
UEnum* Enum = *It;
|
|
|
|
FString SingletonName(GetSingletonName(Enum));
|
|
|
|
CallSingletons.Logf(TEXT(" OuterClass->LinkChild(%s);\r\n"), *SingletonName);
|
|
|
|
FString Extern = FString::Printf(TEXT(" %sclass UEnum* %s;\r\n"), *ApiString, *SingletonName);
|
|
SingletonNameToExternDecl.Add(SingletonName, Extern);
|
|
GeneratedFunctionDeclarations.Log(*Extern);
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" UEnum* %s\r\n"), *SingletonName);
|
|
GeneratedFunctionText.Logf(TEXT(" {\r\n"));
|
|
// Enums can either have a UClass or UPackage as outer (if declared in non-UClass header).
|
|
if (Enum->GetOuter()->IsA(UStruct::StaticClass()))
|
|
{
|
|
GeneratedFunctionText.Logf(TEXT(" UClass* Outer=%s;\r\n"), *GetSingletonName(CastChecked<UStruct>(Enum->GetOuter())));
|
|
}
|
|
else
|
|
{
|
|
GeneratedFunctionText.Logf(TEXT(" UPackage* Outer=%s;\r\n"), *GetPackageSingletonName(CastChecked<UPackage>(Enum->GetOuter())));
|
|
}
|
|
GeneratedFunctionText.Logf(TEXT(" static UEnum* ReturnEnum = NULL;\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" if (!ReturnEnum)\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" {\r\n"));
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" ReturnEnum = new(Outer, TEXT(\"%s\"), RF_Public|RF_Transient|RF_Native) UEnum(FPostConstructInitializeProperties());\r\n"), *Enum->GetName());
|
|
GeneratedFunctionText.Logf(TEXT(" TArray<FName> EnumNames;\r\n"));
|
|
for (int32 Index = 0; Index < Enum->NumEnums(); Index++)
|
|
{
|
|
GeneratedFunctionText.Logf(TEXT(" EnumNames.Add(FName(TEXT(\"%s\")));\r\n"), *Enum->GetEnum(Index).ToString());
|
|
}
|
|
GeneratedFunctionText.Logf(TEXT(" ReturnEnum->SetEnums(EnumNames, %s);\r\n"), Enum->IsNamespaceEnum() ? TEXT("true") : TEXT("false"));
|
|
|
|
FString Meta = GetMetaDataCodeForObject(Enum, TEXT("ReturnEnum"), TEXT(" "));
|
|
if (Meta.Len())
|
|
{
|
|
GeneratedFunctionText.Logf(TEXT("#if WITH_METADATA\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" UMetaData* MetaData = ReturnEnum->GetOutermost()->GetMetaData();\r\n"));
|
|
GeneratedFunctionText.Log(*Meta);
|
|
GeneratedFunctionText.Logf(TEXT("#endif\r\n"));
|
|
}
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" }\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" return ReturnEnum;\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" }\r\n"));
|
|
}
|
|
|
|
// structs
|
|
for ( TFieldIterator<UScriptStruct> It(Class, EFieldIteratorFlags::ExcludeSuper); It; ++It )
|
|
{
|
|
UScriptStruct* ScriptStruct = *It;
|
|
UStruct* BaseStruct = ScriptStruct->GetSuperStruct();
|
|
|
|
FString SingletonName(GetSingletonName(ScriptStruct));
|
|
|
|
CallSingletons.Logf(TEXT(" OuterClass->LinkChild(%s);\r\n"), *SingletonName);
|
|
FString Extern = FString::Printf(TEXT(" %sclass UScriptStruct* %s;\r\n"), *ApiString, *SingletonName);
|
|
SingletonNameToExternDecl.Add(SingletonName, Extern);
|
|
GeneratedFunctionDeclarations.Log(*Extern);
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" UScriptStruct* %s\r\n"), *SingletonName);
|
|
GeneratedFunctionText.Logf(TEXT(" {\r\n"));
|
|
|
|
// if this is a no export struct, we will put a local struct here for offset determination
|
|
TArray<UScriptStruct*> Structs;
|
|
FindNoExportStructs(Structs, ScriptStruct);
|
|
if (Structs.Num())
|
|
{
|
|
TGuardValue<bool> Guard(bIsExportingForOffsetDeterminationOnly,true);
|
|
ExportMirrorsForNoexportStructs(Structs, 8, GeneratedFunctionText);
|
|
}
|
|
|
|
// Structs can either have a UClass or UPackage as outer (if delcared in non-UClass header).
|
|
if (ScriptStruct->GetOuter()->IsA(UStruct::StaticClass()))
|
|
{
|
|
GeneratedFunctionText.Logf(TEXT(" UStruct* Outer=%s;\r\n"), *GetSingletonName(CastChecked<UStruct>(ScriptStruct->GetOuter())));
|
|
}
|
|
else
|
|
{
|
|
GeneratedFunctionText.Logf(TEXT(" UPackage* Outer=%s;\r\n"), *GetPackageSingletonName(CastChecked<UPackage>(ScriptStruct->GetOuter())));
|
|
}
|
|
GeneratedFunctionText.Logf(TEXT(" static UScriptStruct* ReturnStruct = NULL;\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" if (!ReturnStruct)\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" {\r\n"));
|
|
FString BaseStructString(TEXT("NULL"));
|
|
if (BaseStruct)
|
|
{
|
|
CastChecked<UScriptStruct>(BaseStruct); // this better actually be a script struct
|
|
BaseStructString = GetSingletonName(BaseStruct);
|
|
}
|
|
FString CppStructOpsString(TEXT("NULL"));
|
|
FString ExplicitSizeString;
|
|
FString ExplicitAlignmentString;
|
|
if ( (ScriptStruct->StructFlags&STRUCT_Native) != 0 )
|
|
{
|
|
//@todo .u we don't need the auto register versions of these (except for hotreload, which should be fixed)
|
|
CppStructOpsString = FString::Printf( TEXT("new UScriptStruct::TCppStructOps<%s>"), NameLookupCPP->GetNameCPP(ScriptStruct) );
|
|
}
|
|
else
|
|
{
|
|
ExplicitSizeString = FString::Printf( TEXT(", sizeof(%s), ALIGNOF(%s)"), NameLookupCPP->GetNameCPP(ScriptStruct), NameLookupCPP->GetNameCPP(ScriptStruct) );
|
|
}
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" ReturnStruct = new(Outer, TEXT(\"%s\"), RF_Public|RF_Transient|RF_Native) UScriptStruct(FPostConstructInitializeProperties(), %s, %s, EStructFlags(0x%08X)%s);\r\n"),
|
|
*ScriptStruct->GetName(),
|
|
*BaseStructString,
|
|
*CppStructOpsString,
|
|
(uint32)(ScriptStruct->StructFlags & ~STRUCT_ComputedFlags),
|
|
*ExplicitSizeString
|
|
);
|
|
TheFlagAudit.Add(ScriptStruct, TEXT("StructFlags"), (uint64)(ScriptStruct->StructFlags & ~STRUCT_ComputedFlags));
|
|
|
|
TArray<UProperty*> Props;
|
|
for ( TFieldIterator<UProperty> ItInner(ScriptStruct, EFieldIteratorFlags::ExcludeSuper); ItInner; ++ItInner )
|
|
{
|
|
Props.Add(*ItInner);
|
|
}
|
|
FString OuterString = FString(TEXT("ReturnStruct"));
|
|
FString Meta = GetMetaDataCodeForObject(ScriptStruct, *OuterString, TEXT(" "));
|
|
OutputProperties(Meta, GeneratedFunctionText, OuterString, Props, TEXT(" "));
|
|
GeneratedFunctionText.Logf(TEXT(" ReturnStruct->StaticLink();\r\n"));
|
|
|
|
if (Meta.Len())
|
|
{
|
|
GeneratedFunctionText.Logf(TEXT("#if WITH_METADATA\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" UMetaData* MetaData = ReturnStruct->GetOutermost()->GetMetaData();\r\n"));
|
|
GeneratedFunctionText.Log(*Meta);
|
|
GeneratedFunctionText.Logf(TEXT("#endif\r\n"));
|
|
}
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" }\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" return ReturnStruct;\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" }\r\n"));
|
|
|
|
}
|
|
|
|
TArray<UFunction*> FunctionsToExport;
|
|
for( TFieldIterator<UFunction> Function(Class,EFieldIteratorFlags::ExcludeSuper); Function; ++Function )
|
|
{
|
|
FunctionsToExport.Add(*Function);
|
|
}
|
|
|
|
// Sort the list of functions
|
|
FunctionsToExport.Sort();
|
|
|
|
// find the class info for this class
|
|
FClassMetaData* ClassData = GScriptHelper->FindClassData(Class);
|
|
|
|
// Export the init code for each function
|
|
for (int32 FuncIndex = 0; FuncIndex < FunctionsToExport.Num(); FuncIndex++)
|
|
{
|
|
UFunction* Function = FunctionsToExport[FuncIndex];
|
|
UFunction* SuperFunction = Function->GetSuperFunction();
|
|
|
|
FFunctionData* CompilerInfo = ClassData->FindFunctionData(Function);
|
|
check(CompilerInfo);
|
|
|
|
const FFuncInfo& FunctionData = CompilerInfo->GetFunctionData();
|
|
|
|
FString SingletonName(GetSingletonName(Function));
|
|
|
|
CallSingletons.Logf(TEXT(" OuterClass->LinkChild(%s);\r\n"), *SingletonName);
|
|
FString Extern = FString::Printf(TEXT(" %sclass UFunction* %s;\r\n"), *ApiString, *SingletonName);
|
|
SingletonNameToExternDecl.Add(SingletonName, Extern);
|
|
GeneratedFunctionDeclarations.Log(*Extern);
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" UFunction* %s\r\n"), *SingletonName);
|
|
GeneratedFunctionText.Logf(TEXT(" {\r\n"));
|
|
|
|
if (bIsNoExport || !(Function->FunctionFlags&FUNC_Event)) // non-events do not export a params struct, so lets do that locally for offset determination
|
|
{
|
|
TGuardValue<bool> Guard(bIsExportingForOffsetDeterminationOnly,true);
|
|
TArray<UScriptStruct*> Structs;
|
|
FindNoExportStructs(Structs, Function);
|
|
if (Structs.Num())
|
|
{
|
|
ExportMirrorsForNoexportStructs(Structs, 8, GeneratedFunctionText);
|
|
}
|
|
TArray<UFunction*> CallbackFunctions;
|
|
CallbackFunctions.Add(Function);
|
|
ExportEventParms(CallbackFunctions, NULL, 8, false, &GeneratedFunctionText);
|
|
}
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" UClass* OuterClass=%s;\r\n"), *GetSingletonName(Class));
|
|
GeneratedFunctionText.Logf(TEXT(" static UFunction* ReturnFunction = NULL;\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" if (!ReturnFunction)\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" {\r\n"));
|
|
FString SuperFunctionString(TEXT("NULL"));
|
|
if (SuperFunction)
|
|
{
|
|
SuperFunctionString = GetSingletonName(SuperFunction);
|
|
}
|
|
TArray<UProperty*> Props;
|
|
for ( TFieldIterator<UProperty> ItInner(Function,EFieldIteratorFlags::ExcludeSuper); ItInner; ++ItInner )
|
|
{
|
|
Props.Add(*ItInner);
|
|
}
|
|
FString StructureSize;
|
|
if (Props.Num())
|
|
{
|
|
UFunction* TempFunction = Function;
|
|
while (TempFunction->GetSuperFunction())
|
|
{
|
|
TempFunction = TempFunction->GetSuperFunction();
|
|
}
|
|
FString FunctionName = TempFunction->GetName();
|
|
if( TempFunction->HasAnyFunctionFlags( FUNC_Delegate ) )
|
|
{
|
|
FunctionName = FunctionName.LeftChop( FString( HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX ).Len() );
|
|
}
|
|
|
|
StructureSize = FString::Printf(TEXT(", sizeof(%s)"), *GetEventStructParamsName(*CastChecked<UClass>(TempFunction->GetOuter())->GetName(), *FunctionName));
|
|
}
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" ReturnFunction = new(OuterClass, TEXT(\"%s\"), RF_Public|RF_Transient|RF_Native) UFunction(FPostConstructInitializeProperties(), %s, 0x%08X, %d%s);\r\n"),
|
|
*Function->GetName(),
|
|
*SuperFunctionString,
|
|
Function->FunctionFlags,
|
|
(uint32)Function->RepOffset,
|
|
*StructureSize
|
|
);
|
|
TheFlagAudit.Add(Function, TEXT("FunctionFlags"), Function->FunctionFlags);
|
|
|
|
|
|
FString OuterString = FString(TEXT("ReturnFunction"));
|
|
FString Meta = GetMetaDataCodeForObject(Function, *OuterString, TEXT(" "));
|
|
|
|
for (int32 Index = Props.Num() - 1; Index >= 0; Index--)
|
|
{
|
|
OutputProperty(Meta, GeneratedFunctionText, OuterString, Props[Index], TEXT(" "));
|
|
}
|
|
|
|
if (FunctionData.FunctionFlags & (FUNC_NetRequest | FUNC_NetResponse))
|
|
{
|
|
GeneratedFunctionText.Logf(TEXT(" ReturnFunction->RPCId=%d;\r\n"), FunctionData.RPCId);
|
|
GeneratedFunctionText.Logf(TEXT(" ReturnFunction->RPCResponseId=%d;\r\n"), FunctionData.RPCResponseId);
|
|
}
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" ReturnFunction->Bind();\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" ReturnFunction->StaticLink();\r\n"));
|
|
|
|
if (Meta.Len())
|
|
{
|
|
GeneratedFunctionText.Logf(TEXT("#if WITH_METADATA\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" UMetaData* MetaData = ReturnFunction->GetOutermost()->GetMetaData();\r\n"));
|
|
GeneratedFunctionText.Log(*Meta);
|
|
GeneratedFunctionText.Logf(TEXT("#endif\r\n"));
|
|
}
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" }\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" return ReturnFunction;\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" }\r\n"));
|
|
}
|
|
|
|
// The class itself (unless it's a temporary UHT class).
|
|
if (!Class->HasAnyClassFlags(CLASS_Temporary))
|
|
{
|
|
// simple ::StaticClass wrapper to avoid header, link and DLL hell
|
|
{
|
|
FString SingletonNameNoRegister(GetSingletonName(Class, false));
|
|
|
|
FString Extern = FString::Printf(TEXT(" %sclass UClass* %s;\r\n"), *ApiString, *SingletonNameNoRegister);
|
|
SingletonNameToExternDecl.Add(SingletonNameNoRegister, Extern);
|
|
GeneratedFunctionDeclarations.Log(*Extern);
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" UClass* %s\r\n"), *SingletonNameNoRegister);
|
|
GeneratedFunctionText.Logf(TEXT(" {\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" return %s::StaticClass();\r\n"), NameLookupCPP->GetNameCPP(Class));
|
|
GeneratedFunctionText.Logf(TEXT(" }\r\n"));
|
|
|
|
|
|
}
|
|
FString SingletonName(GetSingletonName(Class));
|
|
|
|
FriendText.Logf(TEXT(" friend %sclass UClass* %s;\r\n"), *ApiString, *SingletonName);
|
|
FString Extern = FString::Printf(TEXT(" %sclass UClass* %s;\r\n"), *ApiString, *SingletonName);
|
|
SingletonNameToExternDecl.Add(SingletonName, Extern);
|
|
GeneratedFunctionDeclarations.Log(*Extern);
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" UClass* %s\r\n"), *SingletonName);
|
|
GeneratedFunctionText.Logf(TEXT(" {\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" static UClass* OuterClass = NULL;\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" if (!OuterClass)\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" {\r\n"));
|
|
if (Class->GetSuperClass() && Class->GetSuperClass() != Class)
|
|
{
|
|
GeneratedFunctionText.Logf(TEXT(" %s;\r\n"), *GetSingletonName(Class->GetSuperClass()));
|
|
}
|
|
GeneratedFunctionText.Logf(TEXT(" %s;\r\n"), *GetPackageSingletonName(CastChecked<UPackage>(Class->GetOutermost())));
|
|
GeneratedFunctionText.Logf(TEXT(" OuterClass = %s::StaticClass();\r\n"), NameLookupCPP->GetNameCPP(Class));
|
|
GeneratedFunctionText.Logf(TEXT(" UObjectForceRegistration(OuterClass);\r\n"));
|
|
uint32 Flags = Class->ClassFlags & CLASS_SaveInCompiledInClasses;
|
|
GeneratedFunctionText.Logf(TEXT(" OuterClass->ClassFlags |= 0x%08X;\r\n"), Flags);
|
|
//if (Class->IsChildOf(UObject::StaticClass()))
|
|
//{
|
|
// GeneratedFunctionText.Logf(TEXT(" OuterClass->ClassAddReferencedObjects = &%s::AddReferencedObjects;\r\n"), NameLookupCPP->GetNameCPP(Class));
|
|
//}
|
|
TheFlagAudit.Add(Class, TEXT("ClassFlags"), Flags);
|
|
GeneratedFunctionText.Logf(TEXT("\r\n"));
|
|
GeneratedFunctionText.Log(CallSingletons);
|
|
GeneratedFunctionText.Logf(TEXT("\r\n"));
|
|
|
|
FString OuterString = FString(TEXT("OuterClass"));
|
|
FString Meta = GetMetaDataCodeForObject(Class, *OuterString, TEXT(" "));
|
|
// properties
|
|
{
|
|
TArray<UProperty*> Props;
|
|
for ( TFieldIterator<UProperty> ItInner(Class,EFieldIteratorFlags::ExcludeSuper); ItInner; ++ItInner )
|
|
{
|
|
Props.Add(*ItInner);
|
|
}
|
|
OutputProperties(Meta, GeneratedFunctionText, OuterString, Props, TEXT(" "));
|
|
}
|
|
// function table
|
|
{
|
|
|
|
// Grab and sort the list of functions in the function map
|
|
TArray<UFunction*> FunctionsInMap;
|
|
for (auto Function : TFieldRange<UFunction>(Class, EFieldIteratorFlags::ExcludeSuper))
|
|
{
|
|
FunctionsInMap.Add(Function);
|
|
}
|
|
FunctionsInMap.Sort();
|
|
|
|
// Emit code to construct each UFunction and rebuild the function map at runtime
|
|
for (UFunction* Function : FunctionsInMap)
|
|
{
|
|
GeneratedFunctionText.Logf(TEXT(" OuterClass->AddFunctionToFunctionMap(%s);\r\n"), *GetSingletonName(Function));
|
|
}
|
|
}
|
|
|
|
// class flags are handled by the intrinsic bootstrap code
|
|
//GeneratedFunctionText.Logf(TEXT(" OuterClass->ClassFlags = 0x%08X;\r\n"), Class->ClassFlags);
|
|
if (Class->ClassConfigName != NAME_None)
|
|
{
|
|
GeneratedFunctionText.Logf(TEXT(" OuterClass->ClassConfigName = FName(TEXT(\"%s\"));\r\n"), *Class->ClassConfigName.ToString());
|
|
}
|
|
|
|
for (auto& Inter : Class->Interfaces)
|
|
{
|
|
check(Inter.Class);
|
|
FString OffsetString(TEXT("0"));
|
|
if (Inter.PointerOffset)
|
|
{
|
|
OffsetString = FString::Printf(TEXT("VTABLE_OFFSET(%s, %s)"), NameLookupCPP->GetNameCPP(Class), NameLookupCPP->GetNameCPP(Inter.Class, true));
|
|
}
|
|
GeneratedFunctionText.Logf(TEXT(" OuterClass->Interfaces.Add(FImplementedInterface(%s, %s, %s ));\r\n"),
|
|
*GetSingletonName(Inter.Class, false),
|
|
*OffsetString,
|
|
Inter.bImplementedByK2 ? TEXT("true") : TEXT("false")
|
|
);
|
|
}
|
|
if (Class->ClassGeneratedBy)
|
|
{
|
|
UE_LOG(LogCompile, Fatal, TEXT("For intrinsic and compiled-in classes, ClassGeneratedBy should always be NULL"));
|
|
GeneratedFunctionText.Logf(TEXT(" OuterClass->ClassGeneratedBy = %s;\r\n"), *GetSingletonName(CastChecked<UClass>(Class->ClassGeneratedBy), false));
|
|
}
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" OuterClass->StaticLink();\r\n"));
|
|
|
|
if (Meta.Len())
|
|
{
|
|
GeneratedFunctionText.Logf(TEXT("#if WITH_METADATA\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" UMetaData* MetaData = OuterClass->GetOutermost()->GetMetaData();\r\n"));
|
|
GeneratedFunctionText.Log(*Meta);
|
|
GeneratedFunctionText.Logf(TEXT("#endif\r\n"));
|
|
}
|
|
|
|
GeneratedFunctionText.Logf(TEXT(" }\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" check(OuterClass->GetClass());\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" return OuterClass;\r\n"));
|
|
GeneratedFunctionText.Logf(TEXT(" }\r\n"));
|
|
}
|
|
if (FriendText.Len())
|
|
{
|
|
if (bIsNoExport)
|
|
{
|
|
GeneratedFunctionText.Logf(TEXT(" /* friend declarations for pasting into noexport class %s\r\n"), NameLookupCPP->GetNameCPP(Class));
|
|
GeneratedFunctionText.Log(FriendText);
|
|
GeneratedFunctionText.Logf(TEXT(" */\r\n"));
|
|
FriendText.Empty();
|
|
}
|
|
}
|
|
FString SingletonName(GetSingletonName(Class));
|
|
SingletonName.ReplaceInline(TEXT("()"), TEXT("")); // function address
|
|
|
|
// Skip temporary UHT classes.
|
|
if (!Class->HasAnyClassFlags(CLASS_Temporary))
|
|
{
|
|
GeneratedFunctionText.Logf(TEXT(" static FCompiledInDefer Z_CompiledInDefer_UClass_%s(%s);\r\n"), NameLookupCPP->GetNameCPP(Class), *SingletonName);
|
|
}
|
|
}
|
|
|
|
void FNativeClassHeaderGenerator::ExportNatives(UClass* Class)
|
|
{
|
|
// Skip temporary UHT classes.
|
|
if (Class->HasAnyClassFlags(CLASS_NoExport|CLASS_Temporary))
|
|
return;
|
|
|
|
GeneratedPackageCPP.Logf(TEXT(" void %s::StaticRegisterNatives%s()\r\n"),NameLookupCPP->GetNameCPP(Class),NameLookupCPP->GetNameCPP(Class));
|
|
GeneratedPackageCPP.Logf(TEXT(" {\r\n"));
|
|
|
|
{
|
|
TArray<UFunction*> FunctionsToExport;
|
|
for (auto* Function : TFieldRange<UFunction>(Class, EFieldIteratorFlags::ExcludeSuper))
|
|
{
|
|
if( (Function->FunctionFlags & (FUNC_Native | FUNC_NetRequest)) == FUNC_Native )
|
|
{
|
|
FunctionsToExport.Add(Function);
|
|
}
|
|
}
|
|
|
|
FunctionsToExport.Sort();
|
|
|
|
for (auto Func : FunctionsToExport)
|
|
{
|
|
GeneratedPackageCPP.Logf(TEXT(" FNativeFunctionRegistrar::RegisterFunction(%s::StaticClass(),\"%s\",(Native)&%s::exec%s);\r\n"),
|
|
NameLookupCPP->GetNameCPP(Class),
|
|
*Func->GetName(),
|
|
Class->HasAnyClassFlags(CLASS_Interface) ? *FString::Printf(TEXT("I%s"), *Class->GetName()) : NameLookupCPP->GetNameCPP(Class),
|
|
*Func->GetName()
|
|
);
|
|
}
|
|
}
|
|
|
|
for (auto* Struct : TFieldRange<UScriptStruct>(Class, EFieldIteratorFlags::ExcludeSuper))
|
|
{
|
|
if (Struct->StructFlags & STRUCT_Native)
|
|
{
|
|
GeneratedPackageCPP.Logf( TEXT(" UScriptStruct::DeferCppStructOps(FName(TEXT(\"%s\")),new UScriptStruct::TCppStructOps<%s%s>);\r\n"), *Struct->GetName(), Struct->GetPrefixCPP(), *Struct->GetName() );
|
|
}
|
|
}
|
|
|
|
GeneratedPackageCPP.Logf(TEXT(" }\r\n"));
|
|
}
|
|
|
|
void FNativeClassHeaderGenerator::ExportInterfaceCallFunctions( const TArray<UFunction*>& InCallbackFunctions, FStringOutputDevice& HeaderOutput )
|
|
{
|
|
FClassMetaData* ClassData = GScriptHelper->FindClassData(CurrentClass);
|
|
|
|
TArray<UFunction*> CallbackFunctions = InCallbackFunctions;
|
|
CallbackFunctions.Sort();
|
|
|
|
for (auto FuncIt = CallbackFunctions.CreateConstIterator(); FuncIt; ++FuncIt)
|
|
{
|
|
UFunction* Function = *FuncIt;
|
|
FString FunctionName = Function->GetName();
|
|
FString ClassName = CurrentClass->GetName();
|
|
|
|
FFunctionData* CompilerInfo = ClassData->FindFunctionData(Function);
|
|
check(CompilerInfo);
|
|
|
|
const FFuncInfo& FunctionData = CompilerInfo->GetFunctionData();
|
|
const TCHAR* ConstQualifier = FunctionData.FunctionReference->HasAllFunctionFlags(FUNC_Const) ? TEXT("const ") : TEXT("");
|
|
FString ExtraParam = FString::Printf(TEXT("%sUObject* O"), ConstQualifier);
|
|
|
|
ExportNativeFunctionHeader(FunctionData, HeaderOutput, EExportFunctionType::Interface, EExportFunctionHeaderStyle::Declaration, *ExtraParam);
|
|
HeaderOutput.Logf( TEXT(";%s"), LINE_TERMINATOR );
|
|
|
|
|
|
ExportNativeFunctionHeader(FunctionData, GeneratedPackageCPP, EExportFunctionType::Interface, EExportFunctionHeaderStyle::Definition, *ExtraParam);
|
|
GeneratedPackageCPP.Logf( TEXT("%s%s{%s"), LINE_TERMINATOR, FCString::Spc(4), LINE_TERMINATOR );
|
|
|
|
GeneratedPackageCPP.Logf(TEXT("%scheck(O != NULL);%s"), FCString::Spc(8), LINE_TERMINATOR);
|
|
GeneratedPackageCPP.Logf(TEXT("%scheck(O->GetClass()->ImplementsInterface(U%s::StaticClass()));%s"), FCString::Spc(8), *ClassName, LINE_TERMINATOR);
|
|
|
|
auto Parameters = GetFunctionParmsAndReturn(FunctionData.FunctionReference);
|
|
|
|
// See if we need to create Parms struct
|
|
bool bHasParms = Parameters.HasParms();
|
|
if (bHasParms)
|
|
{
|
|
FString EventParmStructName = GetEventStructParamsName(*Function->GetOwnerClass()->GetName(), *FunctionName);
|
|
GeneratedPackageCPP.Logf(TEXT("%s%s Parms;%s"), FCString::Spc(8), *EventParmStructName, LINE_TERMINATOR);
|
|
}
|
|
|
|
GeneratedPackageCPP.Logf(TEXT("%sUFunction* const Func = O->FindFunction(%s_%s);%s"), FCString::Spc(8), *API, *FunctionName, LINE_TERMINATOR);
|
|
GeneratedPackageCPP.Logf(TEXT("%sif (Func)%s"), FCString::Spc(8), LINE_TERMINATOR);
|
|
GeneratedPackageCPP.Logf(TEXT("%s{%s"), FCString::Spc(8), LINE_TERMINATOR);
|
|
|
|
// code to populate Parms struct
|
|
for (auto It = Parameters.Parms.CreateConstIterator(); It; ++It)
|
|
{
|
|
UProperty* Param = *It;
|
|
|
|
GeneratedPackageCPP.Logf(TEXT("%sParms.%s=%s;%s"), FCString::Spc(12), *Param->GetName(), *Param->GetName(), LINE_TERMINATOR);
|
|
}
|
|
|
|
GeneratedPackageCPP.Logf(TEXT("%sO->ProcessEvent(Func, %s);%s"), FCString::Spc(12), bHasParms ? TEXT("&Parms") : TEXT("NULL"), LINE_TERMINATOR);
|
|
|
|
for (auto It = Parameters.Parms.CreateConstIterator(); It; ++It)
|
|
{
|
|
UProperty* Param = *It;
|
|
|
|
if( Param->HasAllPropertyFlags(CPF_OutParm) && !Param->HasAnyPropertyFlags(CPF_ConstParm|CPF_ReturnParm))
|
|
{
|
|
GeneratedPackageCPP.Logf(TEXT("%s%s=Parms.%s;%s"), FCString::Spc(12), *Param->GetName(), *Param->GetName(), LINE_TERMINATOR);
|
|
}
|
|
}
|
|
|
|
GeneratedPackageCPP.Logf(TEXT("%s}%s"), FCString::Spc(8), LINE_TERMINATOR);
|
|
|
|
|
|
// else clause to call back into native if it's a BlueprintNativeEvent
|
|
if (Function->FunctionFlags & FUNC_Native)
|
|
{
|
|
GeneratedPackageCPP.Logf(TEXT("%selse if (auto I = (%sI%s*)(O->GetNativeInterfaceAddress(U%s::StaticClass())))%s"), FCString::Spc(8), ConstQualifier, *ClassName, *ClassName, LINE_TERMINATOR);
|
|
GeneratedPackageCPP.Logf(TEXT("%s{%s"), FCString::Spc(8), LINE_TERMINATOR);
|
|
|
|
GeneratedPackageCPP.Logf(FCString::Spc(12));
|
|
if (Parameters.Return)
|
|
{
|
|
GeneratedPackageCPP.Logf(TEXT("Parms.ReturnValue = "));
|
|
}
|
|
|
|
GeneratedPackageCPP.Logf(TEXT("I->%s_Implementation("), *FunctionName);
|
|
|
|
bool First = true;
|
|
for (auto It = Parameters.Parms.CreateConstIterator(); It; ++It)
|
|
{
|
|
UProperty* Param = *It;
|
|
|
|
if (!First)
|
|
{
|
|
GeneratedPackageCPP.Logf(TEXT(","));
|
|
}
|
|
First = false;
|
|
|
|
GeneratedPackageCPP.Logf(TEXT("%s"), *Param->GetName());
|
|
}
|
|
|
|
GeneratedPackageCPP.Logf(TEXT(");%s"), LINE_TERMINATOR);
|
|
|
|
GeneratedPackageCPP.Logf(TEXT("%s}%s"), FCString::Spc(8), LINE_TERMINATOR);
|
|
}
|
|
|
|
if (Parameters.Return)
|
|
{
|
|
GeneratedPackageCPP.Logf(TEXT("%sreturn Parms.ReturnValue;%s"), FCString::Spc(8), LINE_TERMINATOR);
|
|
}
|
|
|
|
GeneratedPackageCPP.Logf(TEXT("%s}%s"), FCString::Spc(4), LINE_TERMINATOR);
|
|
|
|
//
|
|
// also generate an empty implementation for the base event function, since we did the Execute_ version above
|
|
//
|
|
ExportNativeFunctionHeader(FunctionData, GeneratedPackageCPP, EExportFunctionType::Event, EExportFunctionHeaderStyle::Definition, NULL);
|
|
GeneratedPackageCPP.Logf(TEXT("%s%s{%s"), LINE_TERMINATOR, FCString::Spc(4), LINE_TERMINATOR);
|
|
|
|
// assert if this is ever called directly
|
|
GeneratedPackageCPP.Logf(TEXT("%scheck(0 && \"Do not directly call Event functions in Interfaces. Call Execute_%s instead.\");%s"), FCString::Spc(8), *FunctionName, LINE_TERMINATOR);
|
|
|
|
// satisfy compiler if it's expecting a return value
|
|
if (Parameters.Return)
|
|
{
|
|
FString EventParmStructName = GetEventStructParamsName(*Function->GetOwnerClass()->GetName(), *FunctionName);
|
|
GeneratedPackageCPP.Logf(TEXT("%s%s Parms;%s"), FCString::Spc(8), *EventParmStructName, LINE_TERMINATOR);
|
|
GeneratedPackageCPP.Logf(TEXT("%sreturn Parms.ReturnValue;%s"), FCString::Spc(8), LINE_TERMINATOR);
|
|
}
|
|
GeneratedPackageCPP.Logf(TEXT("%s}%s"), FCString::Spc(4), LINE_TERMINATOR);
|
|
}
|
|
}
|
|
|
|
void FNativeClassHeaderGenerator::ExportNames()
|
|
{
|
|
// Skip temporary UHT classes.
|
|
if (CurrentClass->HasAnyClassFlags(CLASS_NoExport|CLASS_Temporary))
|
|
{
|
|
return;
|
|
}
|
|
GeneratedHeaderText.Logf(
|
|
TEXT("#ifdef %s_%s_generated_h") LINE_TERMINATOR
|
|
TEXT("#error \"%s.generated.h already included, missing '#pragma once' in %s.h\"") LINE_TERMINATOR
|
|
TEXT("#endif") LINE_TERMINATOR
|
|
TEXT("#define %s_%s_generated_h") LINE_TERMINATOR
|
|
LINE_TERMINATOR,
|
|
*API, *CurrentClass->GetName(), *CurrentClass->GetName(), *CurrentClass->GetName(), *API, *CurrentClass->GetName()
|
|
);
|
|
|
|
TSet<FName> ClassReferencedNames;
|
|
for( TFieldIterator<UFunction> Function(CurrentClass,EFieldIteratorFlags::ExcludeSuper); Function; ++Function )
|
|
{
|
|
if ( Function->HasAnyFunctionFlags(FUNC_Event) && !Function->GetSuperFunction() )
|
|
{
|
|
FName FunctionName = Function->GetFName();
|
|
ClassReferencedNames.Add(FunctionName);
|
|
ReferencedNames.Add(FunctionName);
|
|
}
|
|
}
|
|
if (!ClassReferencedNames.Num())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<FString> Names;
|
|
for (const auto& Name : ClassReferencedNames)
|
|
{
|
|
Names.Add(Name.ToString());
|
|
}
|
|
Names.Sort();
|
|
|
|
for( int32 NameIndex=0; NameIndex<Names.Num(); NameIndex++ )
|
|
{
|
|
GeneratedHeaderText.Logf( TEXT("extern %s_API FName %s_%s;") LINE_TERMINATOR, *API, *API, *Names[NameIndex] );
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Exports the C++ class declarations for a native interface class.
|
|
*/
|
|
void FNativeClassHeaderGenerator::ExportInterfaceClassDeclaration( UClass* Class )
|
|
{
|
|
FClassMetaData* ClassData = GScriptHelper->FindClassData(CurrentClass);
|
|
|
|
TArray<UEnum*> Enums;
|
|
TArray<UScriptStruct*> Structs;
|
|
TArray<UFunction*> CallbackFunctions;
|
|
TArray<UFunction*> NativeFunctions;
|
|
TArray<UFunction*> DelegateFunctions;
|
|
|
|
// get the lists of fields that belong to this class that should be exported
|
|
RetrieveRelevantFields(Class, Enums, Structs, CallbackFunctions, NativeFunctions, DelegateFunctions);
|
|
|
|
ExportNames();
|
|
|
|
// export enum declarations
|
|
ExportEnums(Enums);
|
|
|
|
// export delegate definitions
|
|
ExportDelegateDefinitions(DelegateFunctions, false);
|
|
|
|
// Export enums declared in non-UClass headers.
|
|
ExportGeneratedEnumsInitCode(Enums);
|
|
|
|
// export boilerplate macros for struct
|
|
ExportGeneratedStructBodyMacros(Structs);
|
|
|
|
// export parameters structs for all events and delegates
|
|
ExportEventParms(CallbackFunctions, TEXT(""));
|
|
|
|
// export delegate wrapper function implementations
|
|
ExportDelegateDefinitions(DelegateFunctions, true);
|
|
|
|
UClass* SuperClass = Class->GetSuperClass();
|
|
|
|
|
|
// the name for the C++ version of the UClass
|
|
const TCHAR* ClassCPPName = NameLookupCPP->GetNameCPP( Class );
|
|
const TCHAR* SuperClassCPPName = NameLookupCPP->GetNameCPP( SuperClass );
|
|
|
|
// =============================================
|
|
// Export the UClass declaration
|
|
// Class definition.
|
|
ExportNatives(Class);
|
|
ExportNativeGeneratedInitCode(Class);
|
|
|
|
{
|
|
FStringOutputDevice UInterfaceBoilerplate;
|
|
|
|
// Export the class's native function registration.
|
|
UInterfaceBoilerplate.Logf(TEXT(" private:\r\n"));
|
|
UInterfaceBoilerplate.Logf(TEXT(" static void StaticRegisterNatives%s();\r\n"),NameLookupCPP->GetNameCPP(Class));
|
|
UInterfaceBoilerplate.Log(*FriendText);
|
|
FriendText.Empty();
|
|
UInterfaceBoilerplate.Logf( TEXT("public:\r\n") );
|
|
|
|
// Build the DECLARE_CLASS line
|
|
FString APIArg(API);
|
|
if( !Class->HasAnyClassFlags( CLASS_MinimalAPI ) )
|
|
{
|
|
APIArg = TEXT("NO");
|
|
}
|
|
|
|
const bool bCastedClass = Class->HasAnyCastFlag(CASTCLASS_AllFlags) && SuperClass && Class->ClassCastFlags != SuperClass->ClassCastFlags;
|
|
UInterfaceBoilerplate.Logf( TEXT(" DECLARE_CLASS(%s, %s, COMPILED_IN_FLAGS(CLASS_Abstract%s), %s, %s, %s_API)\r\n"),
|
|
ClassCPPName,
|
|
SuperClassCPPName,
|
|
*GetClassFlagExportText(Class),
|
|
bCastedClass ? *FString::Printf(TEXT("CASTCLASS_%s"), ClassCPPName) : TEXT("0"),
|
|
*FPackageName::GetShortName(Class->GetOuter()->GetName()),
|
|
*APIArg );
|
|
if (!Class->HasAnyClassFlags(CLASS_CustomConstructor))
|
|
{
|
|
UInterfaceBoilerplate.Logf(TEXT(" %s_API %s(const class FPostConstructInitializeProperties& PCIP);\r\n"), *APIArg, ClassCPPName);
|
|
}
|
|
UInterfaceBoilerplate.Logf(TEXT(" DECLARE_SERIALIZER(%s)\r\n"), ClassCPPName);
|
|
UInterfaceBoilerplate.Log(TEXT(" enum {IsIntrinsic=COMPILED_IN_INTRINSIC};\r\n"));
|
|
|
|
if (Class->ClassWithin != Class->GetSuperClass()->ClassWithin)
|
|
{
|
|
UInterfaceBoilerplate.Logf(TEXT(" DECLARE_WITHIN(%s)\r\n"), NameLookupCPP->GetNameCPP( Class->ClassWithin ) );
|
|
}
|
|
|
|
FString MacroName = TEXT("GENERATED_UINTERFACE_BODY()");
|
|
FString Macroized = Macroize(*MacroName, *UInterfaceBoilerplate);
|
|
GeneratedHeaderText.Log(TEXT("#undef GENERATED_UINTERFACE_BODY\r\n"));
|
|
GeneratedHeaderText.Log(*Macroized);
|
|
}
|
|
|
|
// =============================================
|
|
// Export the pure interface version of the class
|
|
|
|
// the name of the pure interface class
|
|
FString InterfaceCPPName = FString::Printf(TEXT("I%s"), *Class->GetName());
|
|
FString SuperInterfaceCPPName;
|
|
if ( SuperClass != NULL )
|
|
{
|
|
SuperInterfaceCPPName = FString::Printf(TEXT("I%s"), *SuperClass->GetName());
|
|
}
|
|
|
|
// C++ -> VM stubs (native function execs)
|
|
ExportNativeFunctions(NativeFunctions);
|
|
|
|
// VM -> C++ proxies (events).
|
|
ExportCallbackFunctions(CallbackFunctions);
|
|
|
|
// Thunk functions
|
|
{
|
|
FStringOutputDevice InterfaceBoilerplate;
|
|
|
|
InterfaceBoilerplate.Logf( TEXT("protected:\r\n\tvirtual ~%s() {}\r\npublic:\r\n"), *InterfaceCPPName );
|
|
InterfaceBoilerplate.Logf( TEXT("\ttypedef %s UClassType;\r\n"), ClassCPPName );
|
|
|
|
ExportInterfaceCallFunctions(CallbackFunctions, InterfaceBoilerplate);
|
|
|
|
// we'll need a way to get to the UObject portion of a native interface, so that we can safely pass native interfaces
|
|
// to script VM functions
|
|
if (SuperClass->IsChildOf(UInterface::StaticClass()))
|
|
{
|
|
InterfaceBoilerplate.Logf(TEXT("\tvirtual UObject* GetUObjectInterface%s()=0;\r\n"), *Class->GetName());
|
|
InterfaceBoilerplate.Logf(TEXT("\tvirtual const UObject* GetUObjectInterface%s() const=0;\r\n"), *Class->GetName());
|
|
}
|
|
|
|
// Replication, add in the declaration for GetLifetimeReplicatedProps() automatically if there are any net flagged properties
|
|
bool bNeedsRep = false;
|
|
for( TFieldIterator<UProperty> It(Class,EFieldIteratorFlags::ExcludeSuper); It; ++It )
|
|
{
|
|
if( (It->PropertyFlags & CPF_Net) != 0 )
|
|
{
|
|
bNeedsRep = true;
|
|
break;
|
|
}
|
|
}
|
|
if (bNeedsRep)
|
|
{
|
|
InterfaceBoilerplate.Logf(TEXT("\tvirtual void GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const OVERRIDE;\r\n"));
|
|
}
|
|
const TCHAR* Suffix = TEXT("_INCLASS_IINTERFACE");
|
|
FString MacroName = FString(NameLookupCPP->GetNameCPP(Class)) + Suffix;
|
|
FString Macroized = Macroize(*MacroName, *InterfaceBoilerplate);
|
|
GeneratedHeaderText.Log(*Macroized);
|
|
InClassMacroCalls.Logf( TEXT("%s%s\r\n"), FCString::Spc(4), *MacroName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to find an element in a TArray of pointers by GetFName().
|
|
* @param Array TArray of pointers.
|
|
* @param Name Name to search for.
|
|
* @return Index of the found element, or INDEX_NONE if not found.
|
|
*/
|
|
template <typename T>
|
|
int32 FindByIndirectName( const TArray<T>& Array, const FName& Name )
|
|
{
|
|
for ( int32 Index=0; Index < Array.Num(); ++Index )
|
|
{
|
|
if ( Array[Index]->GetFName() == Name )
|
|
{
|
|
return Index;
|
|
}
|
|
}
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
/**
|
|
* Appends the header definition for an inheritance hierarchy of classes to the header.
|
|
* Wrapper for ExportClassHeaderRecursive().
|
|
*
|
|
* @param Class The class to be exported.
|
|
*/
|
|
void FNativeClassHeaderGenerator::ExportClassHeader( UClass* Class )
|
|
{
|
|
TArray<UClass*> DependencyChain;
|
|
VisitedMap.Empty( Classes.Num() );
|
|
ExportClassHeaderRecursive( Class, DependencyChain, false );
|
|
}
|
|
|
|
|
|
void FNativeClassHeaderGenerator::ExportClassHeaderInner(UClass* Class, bool bValidNonTemporaryClass)
|
|
{
|
|
if ( !Class->HasAnyClassFlags(CLASS_Interface) )
|
|
{
|
|
TArray<UEnum*> Enums;
|
|
TArray<UScriptStruct*> Structs;
|
|
TArray<UFunction*> CallbackFunctions;
|
|
TArray<UFunction*> NativeFunctions;
|
|
TArray<UFunction*> DelegateFunctions;
|
|
|
|
// get the lists of fields that belong to this class that should be exported
|
|
RetrieveRelevantFields(Class, Enums, Structs, CallbackFunctions, NativeFunctions, DelegateFunctions);
|
|
|
|
ExportNames();
|
|
|
|
// export enum declarations
|
|
ExportEnums(Enums);
|
|
|
|
// export delegate definitions
|
|
ExportDelegateDefinitions(DelegateFunctions, false);
|
|
|
|
// Export enums declared in non-UClass headers.
|
|
ExportGeneratedEnumsInitCode(Enums);
|
|
|
|
// export boilerplate macros for structs
|
|
ExportGeneratedStructBodyMacros(Structs);
|
|
|
|
if (bValidNonTemporaryClass)
|
|
{
|
|
// export parameters structs for all events and delegates
|
|
ExportEventParms(CallbackFunctions, TEXT(""));
|
|
|
|
// export .proto files for any net service functions
|
|
ExportProtoMessage(CallbackFunctions);
|
|
|
|
// export .java files for any net service functions
|
|
ExportMCPMessage(CallbackFunctions);
|
|
|
|
// export delegate wrapper function implementations
|
|
ExportDelegateDefinitions(DelegateFunctions, true);
|
|
|
|
// Class definition.
|
|
ExportNatives(Class);
|
|
}
|
|
|
|
ExportNativeGeneratedInitCode(Class);
|
|
|
|
if (bValidNonTemporaryClass)
|
|
{
|
|
// C++ -> VM stubs (native function execs)
|
|
ExportNativeFunctions(NativeFunctions);
|
|
|
|
// VM -> C++ proxies (events and delegates).
|
|
ExportCallbackFunctions(CallbackFunctions);
|
|
|
|
UClass* SuperClass = Class->GetSuperClass();
|
|
|
|
const TCHAR* ClassCPPName = NameLookupCPP->GetNameCPP( Class );
|
|
const TCHAR* SuperClassCPPName = (SuperClass != NULL) ? NameLookupCPP->GetNameCPP( SuperClass ) : NULL;
|
|
|
|
{
|
|
FStringOutputDevice ClassBoilerplate;
|
|
|
|
// Export the class's native function registration.
|
|
ClassBoilerplate.Logf(TEXT(" private:\r\n"));
|
|
ClassBoilerplate.Logf(TEXT(" static void StaticRegisterNatives%s();\r\n"),NameLookupCPP->GetNameCPP(Class));
|
|
ClassBoilerplate.Log(FriendText);
|
|
FriendText.Empty();
|
|
ClassBoilerplate.Logf(TEXT(" public:\r\n"));
|
|
|
|
// Build the DECLARE_CLASS line
|
|
FString APIArg(API);
|
|
if( !Class->HasAnyClassFlags( CLASS_MinimalAPI ) )
|
|
{
|
|
APIArg = TEXT("NO");
|
|
}
|
|
const bool bCastedClass = Class->HasAnyCastFlag(CASTCLASS_AllFlags) && SuperClass && Class->ClassCastFlags != SuperClass->ClassCastFlags;
|
|
ClassBoilerplate.Logf( TEXT(" DECLARE_CLASS(%s, %s, COMPILED_IN_FLAGS(%s%s), %s, %s, %s_API)\r\n"),
|
|
ClassCPPName,
|
|
SuperClassCPPName ? SuperClassCPPName : TEXT("None"),
|
|
Class->HasAnyClassFlags(CLASS_Abstract) ? TEXT("CLASS_Abstract") : TEXT("0"),
|
|
*GetClassFlagExportText(Class),
|
|
bCastedClass ? *FString::Printf(TEXT("CASTCLASS_%s"), ClassCPPName) : TEXT("0"),
|
|
*FPackageName::GetShortName(*Class->GetOuter()->GetName()),
|
|
*APIArg );
|
|
if (!Class->HasAnyClassFlags(CLASS_CustomConstructor))
|
|
{
|
|
ClassBoilerplate.Logf(TEXT(" /** Standard constructor, called after all reflected properties have been initialized */"));
|
|
ClassBoilerplate.Logf(TEXT(" %s_API %s(const class FPostConstructInitializeProperties& PCIP);\r\n"), *APIArg, ClassCPPName);
|
|
}
|
|
ClassBoilerplate.Logf(TEXT(" DECLARE_SERIALIZER(%s)\r\n"), ClassCPPName);
|
|
ClassBoilerplate.Log(TEXT(" /** Indicates whether the class is compiled into the engine */"));
|
|
ClassBoilerplate.Log(TEXT(" enum {IsIntrinsic=COMPILED_IN_INTRINSIC};\r\n"));
|
|
|
|
if(SuperClass && Class->ClassWithin != SuperClass->ClassWithin)
|
|
{
|
|
ClassBoilerplate.Logf(TEXT(" DECLARE_WITHIN(%s)\r\n"), NameLookupCPP->GetNameCPP( Class->ClassWithin ) );
|
|
}
|
|
|
|
// export the class's config name
|
|
if ( SuperClass && Class->ClassConfigName != NAME_None && Class->ClassConfigName != SuperClass->ClassConfigName )
|
|
{
|
|
ClassBoilerplate.Logf(TEXT(" static const TCHAR* StaticConfigName() {return TEXT(\"%s\");}\r\n\r\n"), *Class->ClassConfigName.ToString());
|
|
}
|
|
|
|
{
|
|
// arrays for preventing multiple export of accessor function in situations where the native class
|
|
// implements multiple children of the same interface base class
|
|
TArray<UClass*> UObjectExportedInterfaces;
|
|
TArray<UClass*> ExportedInterfaces;
|
|
|
|
for (TArray<FImplementedInterface>::TIterator It(Class->Interfaces); It; ++It)
|
|
{
|
|
UClass* InterfaceClass = It->Class;
|
|
if ( InterfaceClass->HasAnyClassFlags(CLASS_Native) )
|
|
{
|
|
for ( UClass* IClass = InterfaceClass; IClass && IClass->HasAnyClassFlags(CLASS_Interface); IClass = IClass->GetSuperClass() )
|
|
{
|
|
if ( IClass != UInterface::StaticClass() && !UObjectExportedInterfaces.Contains(IClass) )
|
|
{
|
|
UObjectExportedInterfaces.Add(IClass);
|
|
ClassBoilerplate.Logf(TEXT("%svirtual UObject* GetUObjectInterface%s(){return this;}\r\n"), FCString::Spc(4), *IClass->GetName());
|
|
ClassBoilerplate.Logf(TEXT("%svirtual const UObject* GetUObjectInterface%s() const{return this;}\r\n"), FCString::Spc(4), *IClass->GetName());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Replication, add in the declaration for GetLifetimeReplicatedProps() automatically if there are any net flagged properties
|
|
for( TFieldIterator<UProperty> It(Class,EFieldIteratorFlags::ExcludeSuper); It; ++It )
|
|
{
|
|
if( (It->PropertyFlags & CPF_Net) != 0 )
|
|
{
|
|
ClassBoilerplate.Logf(TEXT("\tvirtual void GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const OVERRIDE;\r\n"));
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
const TCHAR* Suffix = TEXT("_INCLASS");
|
|
FString MacroName = FString(NameLookupCPP->GetNameCPP(Class)) + Suffix;
|
|
FString Macroized = Macroize(*MacroName, *ClassBoilerplate);
|
|
GeneratedHeaderText.Log(*Macroized);
|
|
InClassMacroCalls.Logf( TEXT("%s%s\r\n"), FCString::Spc(4), *MacroName);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// this is an interface class
|
|
ExportInterfaceClassDeclaration(Class);
|
|
}
|
|
}
|
|
|
|
void FNativeClassHeaderGenerator::ExportClassHeaderWrapper( UClass* Class, bool bIsExportClass )
|
|
{
|
|
const bool bTemporaryJunkClass = Class->HasAnyClassFlags(CLASS_Temporary);
|
|
const bool bValidClass = !bTemporaryJunkClass;
|
|
|
|
check(!GeneratedHeaderText.Len());
|
|
ExportClassHeaderInner(Class, bValidClass);
|
|
|
|
if (!GeneratedHeaderText.Len() && !EnumForeachText.Len())
|
|
{
|
|
// Nothing to export
|
|
return;
|
|
}
|
|
|
|
GeneratedHeaderText.Logf(TEXT("#undef UCLASS_CURRENT_FILE_NAME\r\n"));
|
|
GeneratedHeaderText.Logf(TEXT("#define UCLASS_CURRENT_FILE_NAME %s\r\n\r\n\r\n"), NameLookupCPP->GetNameCPP(CurrentClass));
|
|
|
|
if (bValidClass)
|
|
{
|
|
{
|
|
GeneratedHeaderText.Logf(TEXT("#undef UCLASS\r\n"));
|
|
GeneratedHeaderText.Logf(TEXT("#undef UINTERFACE\r\n"));
|
|
FString MacroName = FString::Printf(TEXT("%s(...)"), Class->HasAnyClassFlags(CLASS_Interface) ? TEXT("UINTERFACE") : TEXT("UCLASS"));
|
|
FString Macroized = Macroize(*MacroName, *PrologMacroCalls);
|
|
GeneratedHeaderText.Log(*Macroized);
|
|
PrologMacroCalls.Empty();
|
|
}
|
|
|
|
{
|
|
GeneratedHeaderText.Logf(TEXT("#undef GENERATED_UCLASS_BODY\r\n"));
|
|
GeneratedHeaderText.Logf(TEXT("#undef GENERATED_IINTERFACE_BODY\r\n"));
|
|
FString MacroName = FString::Printf(TEXT("GENERATED_%s_BODY()"), Class->HasAnyClassFlags(CLASS_Interface) ? TEXT("IINTERFACE") : TEXT("UCLASS"));
|
|
FString Macroized = Macroize(*MacroName, *(FString(TEXT("public:\r\n")) + InClassMacroCalls + TEXT("public:\r\n")));
|
|
GeneratedHeaderText.Log(*Macroized);
|
|
InClassMacroCalls.Empty();
|
|
}
|
|
}
|
|
|
|
const FString SourceFilename = GClassSourceFileMap[Class];
|
|
const FString PkgName = FPackageName::GetShortName(Package);
|
|
|
|
FString NewFileName = SourceFilename.EndsWith(TEXT(".h")) ? SourceFilename : (SourceFilename + TEXT(".h"));
|
|
|
|
FString PkgDir;
|
|
FString GeneratedIncludeDirectory;
|
|
if (MakeCommandlet_FindPackageLocation(*PkgName, PkgDir, GeneratedIncludeDirectory) == false)
|
|
{
|
|
UE_LOG(LogCompile, Error, TEXT("Failed to find path for package %s"), *PkgName);
|
|
}
|
|
|
|
if (bIsExportClass)
|
|
{
|
|
const FString ClassHeaderPath(GeneratedIncludeDirectory / GClassHeaderNameWithNoPathMap[Class] + TEXT(".generated.h") );
|
|
|
|
GeneratedHeaderText += EnumForeachText;
|
|
|
|
FStringOutputDevice GeneratedHeaderTextWithCopyright;
|
|
|
|
GeneratedHeaderTextWithCopyright.Log(
|
|
TEXT("// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.\r\n")
|
|
TEXT("/*===========================================================================\r\n")
|
|
TEXT(" C++ class header boilerplate exported from UnrealHeaderTool.\r\n")
|
|
TEXT(" This is automatically generated by the tools.\r\n")
|
|
TEXT(" DO NOT modify this manually! Edit the corresponding .h files instead!\r\n")
|
|
TEXT("===========================================================================*/\r\n"));
|
|
GeneratedHeaderTextWithCopyright.Log(
|
|
LINE_TERMINATOR
|
|
TEXT("#include \"ObjectBase.h\"") LINE_TERMINATOR
|
|
LINE_TERMINATOR);
|
|
GeneratedHeaderTextWithCopyright.Log(*GeneratedHeaderText);
|
|
|
|
SaveHeaderIfChanged(*ClassHeaderPath, *GeneratedHeaderTextWithCopyright);
|
|
|
|
check(SourceFilename.EndsWith(TEXT(".h")));
|
|
|
|
if(bUseRelativePaths)
|
|
{
|
|
// When building Rocket, we need relative paths in the generated headers so that they can be installed to any directory.
|
|
FPaths::MakePathRelativeTo(NewFileName, *ClassHeaderPath);
|
|
NewFileName.ReplaceInline(TEXT("\\"), TEXT("/"));
|
|
}
|
|
// ...we cannot rebase paths relative to the install directory. It may be on a different drive.
|
|
else if (FPaths::MakePathRelativeTo(NewFileName, *GManifest.RootLocalPath))
|
|
{
|
|
NewFileName = GManifest.RootBuildPath / NewFileName;
|
|
}
|
|
|
|
// Keep track of all of the UObject headers for this module, in the same order that we digest them in
|
|
// @todo uht: We're wrapping these includes in checks for header guards, ONLY because most existing UObject headers are missing '#pragma once'
|
|
ListOfAllUObjectHeaderIncludes.Logf(
|
|
TEXT("#ifndef %s_%s_generated_h") LINE_TERMINATOR
|
|
TEXT(" #include \"%s\"") LINE_TERMINATOR
|
|
TEXT("#endif") LINE_TERMINATOR,
|
|
*API, *CurrentClass->GetName(), *NewFileName );
|
|
|
|
// For the 'Classes' mega-header, only include legacy UObjects classes from the 'Classes' folder. Private and Public classes are
|
|
// not included here -- you should include those yourself if you need them!
|
|
const bool bIsPublicClassesHeader = GPublicClassSet.Contains( Class );
|
|
if( bIsPublicClassesHeader )
|
|
{
|
|
ListOfPublicClassesUObjectHeaderIncludes.Logf(TEXT("#include \"%s\"\r\n"), *NewFileName);
|
|
}
|
|
}
|
|
|
|
GeneratedHeaderText.Empty();
|
|
EnumForeachText.Empty();
|
|
}
|
|
|
|
/**
|
|
* Appends the header definition for an inheritance hierarchy of classes to the header.
|
|
*
|
|
* @param Class The class to be exported.
|
|
* @param DependencyChain Used for finding errors. Must be empty before the first call.
|
|
* @param bCheckDependenciesOnly Whether we should just keep checking for dependency errors, without exporting anything.
|
|
*/
|
|
void FNativeClassHeaderGenerator::ExportClassHeaderRecursive( UClass* Class, TArray<UClass*>& DependencyChain, bool bCheckDependenciesOnly )
|
|
{
|
|
bool bIsExportClass = GClassStrippedHeaderTextMap.Contains(Class) && Class->HasAnyClassFlags(CLASS_Native|CLASS_Temporary) && !Class->HasAnyClassFlags(CLASS_NoExport);
|
|
bool bIsCorrectHeader = GClassHeaderFilenameMap.FindRef(Class) == ClassHeaderFilename && Class->GetOuter() == Package;
|
|
|
|
// Check for circular header dependencies between export classes.
|
|
if ( bIsExportClass )
|
|
{
|
|
if ( bIsCorrectHeader == false )
|
|
{
|
|
if ( DependencyChain.Num() == 0 )
|
|
{
|
|
// The first export class we found doesn't belong in this header: No need to keep exporting along this dependency path.
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// From now on, we're not going to export anything. Instead, we're going to check that no deeper dependency tries to export to this header file.
|
|
bCheckDependenciesOnly = true;
|
|
}
|
|
}
|
|
else if ( bCheckDependenciesOnly && !ClassHeaderFilename.IsEmpty() )
|
|
{
|
|
FString DependencyChainString;
|
|
for ( int32 DependencyIndex=0; DependencyIndex < DependencyChain.Num(); DependencyIndex++ )
|
|
{
|
|
UClass* DependencyClass = DependencyChain[DependencyIndex];
|
|
DependencyChainString += DependencyClass->GetName() + TEXT(" -> ");
|
|
}
|
|
DependencyChainString += Class->GetName();
|
|
UE_LOG(LogCompile, Warning, TEXT("Circular header dependency detected (%s), while exporting %s!"), *DependencyChainString, *(FPackageName::GetShortName(Package) + ClassHeaderFilename + TEXT("Classes.h")) );
|
|
}
|
|
}
|
|
|
|
// Check if the Class has already been exported, after we've checked for circular header dependencies.
|
|
if (GExportedClasses.Contains(Class))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check for circular dependencies.
|
|
bool* bVisited = VisitedMap.Find( Class );
|
|
if ( bVisited && *bVisited == true )
|
|
{
|
|
UE_LOG(LogCompile, Error, TEXT("Circular dependency detected for class %s!"), *Class->GetName() );
|
|
return;
|
|
}
|
|
// Temporarily mark the Class as VISITED. Make sure to clear this flag before returning!
|
|
VisitedMap.Add( Class, true );
|
|
|
|
if ( bIsExportClass )
|
|
{
|
|
DependencyChain.Add( Class );
|
|
}
|
|
|
|
// Export the super class first.
|
|
if (UClass* SuperClass = Class->GetSuperClass())
|
|
{
|
|
ExportClassHeaderRecursive(SuperClass, DependencyChain, bCheckDependenciesOnly);
|
|
}
|
|
|
|
// Export all classes we depend on.
|
|
for( auto It = GClassDependentOnMap.FindOrAdd(Class)->CreateConstIterator(); It; ++It )
|
|
{
|
|
const FName& DependencyClassName = *It;
|
|
|
|
// By this point, all classes should have been validated so we can strip the given class name when searching.
|
|
FString DependencyClassNameStripped(DependencyClassName.ToString());
|
|
|
|
// Handle #include directives as 'dependson' (allowing them to fail).
|
|
const bool bClassNameFromHeader = FHeaderParser::DependentClassNameFromHeader(*DependencyClassNameStripped, DependencyClassNameStripped);
|
|
DependencyClassNameStripped = GetClassNameWithPrefixRemoved( DependencyClassNameStripped );
|
|
|
|
int32 ClassIndex = FindByIndirectName( Classes, *DependencyClassNameStripped );
|
|
UClass* FoundClass = FindObject<UClass>(ANY_PACKAGE, *DependencyClassNameStripped);
|
|
// Only export the class if it's in Classes array (it may be in another package).
|
|
UClass* DependsOnClass = (ClassIndex != INDEX_NONE) ? FoundClass : NULL;
|
|
if (FoundClass == NULL)
|
|
{
|
|
// Try again with temporary class name. This may be struct only header.
|
|
DependencyClassNameStripped = GetClassNameWithoutPrefix(DependencyClassName.ToString());
|
|
DependencyClassNameStripped = FHeaderParser::GenerateTemporaryClassName(*DependencyClassNameStripped);
|
|
ClassIndex = FindByIndirectName( Classes, *DependencyClassNameStripped );
|
|
FoundClass = FindObject<UClass>(ANY_PACKAGE, *DependencyClassNameStripped);
|
|
DependsOnClass = (ClassIndex != INDEX_NONE) ? FoundClass : NULL;
|
|
}
|
|
|
|
if (DependsOnClass)
|
|
{
|
|
ExportClassHeaderRecursive(DependsOnClass, DependencyChain, bCheckDependenciesOnly);
|
|
}
|
|
else if ( !FoundClass && !bClassNameFromHeader )
|
|
{
|
|
UE_LOG(LogCompile, Error, TEXT("Unknown class %s used in dependson declaration in class %s!"), *DependencyClassName.ToString(), *Class->GetName() );
|
|
}
|
|
}
|
|
|
|
// Export class header.
|
|
if ( bIsCorrectHeader && !bCheckDependenciesOnly && !Class->HasAnyClassFlags(CLASS_Intrinsic) && Class->HasAnyClassFlags(CLASS_Native|CLASS_Temporary) )
|
|
{
|
|
CurrentClass = Class;
|
|
|
|
// Mark class as exported.
|
|
GExportedClasses.Add(Class);
|
|
|
|
ExportClassHeaderWrapper(Class, bIsExportClass);
|
|
}
|
|
|
|
// We're done visiting this Class.
|
|
VisitedMap.Add( Class, false );
|
|
if ( bIsExportClass )
|
|
{
|
|
DependencyChain.RemoveAtSwap( DependencyChain.Num() - 1 );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a string in the format CLASS_Something|CLASS_Something which represents all class flags that are set for the specified
|
|
* class which need to be exported as part of the DECLARE_CLASS macro
|
|
*/
|
|
FString FNativeClassHeaderGenerator::GetClassFlagExportText( UClass* Class )
|
|
{
|
|
FString StaticClassFlagText;
|
|
|
|
check(Class);
|
|
if ( Class->HasAnyClassFlags(CLASS_Transient) )
|
|
{
|
|
StaticClassFlagText += TEXT(" | CLASS_Transient");
|
|
}
|
|
if( Class->HasAnyClassFlags(CLASS_DefaultConfig) )
|
|
{
|
|
StaticClassFlagText += TEXT(" | CLASS_DefaultConfig");
|
|
}
|
|
if( Class->HasAnyClassFlags(CLASS_Config) )
|
|
{
|
|
StaticClassFlagText += TEXT(" | CLASS_Config");
|
|
}
|
|
if ( Class->HasAnyClassFlags(CLASS_Interface) )
|
|
{
|
|
StaticClassFlagText += TEXT(" | CLASS_Interface");
|
|
}
|
|
if ( Class->HasAnyClassFlags(CLASS_Deprecated) )
|
|
{
|
|
StaticClassFlagText += TEXT(" | CLASS_Deprecated");
|
|
}
|
|
|
|
return StaticClassFlagText;
|
|
}
|
|
|
|
void FNativeClassHeaderGenerator::RetrieveRelevantFields(UClass* Class, TArray<UEnum*>& Enums, TArray<UScriptStruct*>& Structs, TArray<UFunction*>& CallbackFunctions, TArray<UFunction*>& NativeFunctions, TArray<UFunction*>& DelegateFunctions)
|
|
{
|
|
for ( TFieldIterator<UField> It(Class,EFieldIteratorFlags::ExcludeSuper); It; ++It )
|
|
{
|
|
UField* CurrentField = *It;
|
|
UClass* FieldClass = CurrentField->GetClass();
|
|
if ( FieldClass == UEnum::StaticClass() )
|
|
{
|
|
UEnum* Enum = (UEnum*)CurrentField;
|
|
Enums.Add(Enum);
|
|
}
|
|
|
|
else if ( FieldClass == UScriptStruct::StaticClass() )
|
|
{
|
|
UScriptStruct* Struct = (UScriptStruct*)CurrentField;
|
|
Structs.Add(Struct);
|
|
}
|
|
|
|
else if ( FieldClass == UFunction::StaticClass() )
|
|
{
|
|
bool bAdded = false;
|
|
UFunction* Function = (UFunction*)CurrentField;
|
|
|
|
if ( (Function->FunctionFlags&(FUNC_Event)) != 0 &&
|
|
Function->GetSuperFunction() == NULL )
|
|
{
|
|
CallbackFunctions.Add(Function);
|
|
bAdded = true;
|
|
}
|
|
|
|
else if ( (Function->FunctionFlags&(FUNC_Delegate)) != 0 &&
|
|
Function->GetSuperFunction() == NULL )
|
|
{
|
|
DelegateFunctions.Add(Function);
|
|
bAdded = true;
|
|
}
|
|
|
|
if ( (Function->FunctionFlags&FUNC_Native) != 0 )
|
|
{
|
|
NativeFunctions.Add(Function);
|
|
bAdded = true;
|
|
}
|
|
|
|
check(bAdded)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Exports the header text for the list of enums specified
|
|
*
|
|
* @param Enums the enums to export
|
|
*/
|
|
void FNativeClassHeaderGenerator::ExportEnums( const TArray<UEnum*>& Enums )
|
|
{
|
|
// Enum definitions.
|
|
for( int32 EnumIdx = Enums.Num() - 1; EnumIdx >= 0; EnumIdx-- )
|
|
{
|
|
UEnum* Enum = Enums[EnumIdx];
|
|
|
|
FString QualifierPrefix;
|
|
if (!Enum->ActualEnumNameInsideNamespace.IsEmpty())
|
|
{
|
|
QualifierPrefix = Enum->GetName() + TEXT("::");
|
|
}
|
|
|
|
// Export FOREACH macro
|
|
EnumForeachText.Logf( TEXT("#define FOREACH_ENUM_%s(op) "), *Enum->GetName().ToUpper() );
|
|
for (int32 i = 0; i < Enum->NumEnums() - 1; i++)
|
|
{
|
|
const FString QualifiedEnumValue = Enum->GetEnum(i).ToString();
|
|
EnumForeachText.Logf( TEXT("\\\r\n op(%s) "), *QualifiedEnumValue );
|
|
}
|
|
EnumForeachText.Logf( TEXT("\r\n") );
|
|
}
|
|
}
|
|
|
|
// Exports the header text for the list of structs specified (GENERATED_USTRUCT_BODY impls)
|
|
void FNativeClassHeaderGenerator::ExportGeneratedStructBodyMacros(const TArray<UScriptStruct*>& NativeStructs)
|
|
{
|
|
// reverse the order.
|
|
for (int32 i = NativeStructs.Num() - 1; i >= 0; --i)
|
|
{
|
|
UScriptStruct* Struct = NativeStructs[i];
|
|
|
|
// Export struct.
|
|
if (Struct->StructFlags & STRUCT_Native)
|
|
{
|
|
check(Struct->StructMacroDeclaredLineNumber != INDEX_NONE);
|
|
|
|
const FString FriendApiString = FString::Printf(TEXT("%s_API "), *API);
|
|
const FString StaticConstructionString = GetSingletonName(Struct);
|
|
|
|
FString RequiredAPI;
|
|
if (!(Struct->StructFlags & STRUCT_RequiredAPI))
|
|
{
|
|
RequiredAPI = FriendApiString;
|
|
}
|
|
|
|
const FString FriendLine = FString::Printf(TEXT(" friend %sclass UScriptStruct* %s;\r\n"), *FriendApiString, *StaticConstructionString);
|
|
const FString StaticClassLine = FString::Printf(TEXT(" %sstatic class UScriptStruct* StaticStruct();\r\n"), *RequiredAPI);
|
|
|
|
const FString CombinedLine = FriendLine + StaticClassLine;
|
|
const FString MacroName =
|
|
FString::Printf(TEXT("%s_USTRUCT_BODY_LINE_%d"), NameLookupCPP->GetNameCPP(CurrentClass), Struct->StructMacroDeclaredLineNumber);
|
|
|
|
const FString Macroized = Macroize(*MacroName, *CombinedLine);
|
|
GeneratedHeaderText.Log(*Macroized);
|
|
|
|
FString SingletonName = StaticConstructionString.Replace(TEXT("()"), TEXT("")); // function address
|
|
|
|
GeneratedPackageCPP.Logf(TEXT("class UScriptStruct* %s::StaticStruct()\r\n"), NameLookupCPP->GetNameCPP(Struct));
|
|
GeneratedPackageCPP.Logf(TEXT("{\r\n"));
|
|
GeneratedPackageCPP.Logf(TEXT(" static class UScriptStruct* Singleton = NULL;\r\n"));
|
|
GeneratedPackageCPP.Logf(TEXT(" if (!Singleton)\r\n"));
|
|
GeneratedPackageCPP.Logf(TEXT(" {\r\n"));
|
|
GeneratedPackageCPP.Logf(TEXT(" extern %sclass UScriptStruct* %s;\r\n"), *FriendApiString, *StaticConstructionString);
|
|
|
|
// UStructs can have UClass or UPackage outer (if declared in non-UClass headers).
|
|
if (Struct->GetOuter()->IsA(UStruct::StaticClass()))
|
|
{
|
|
GeneratedPackageCPP.Logf(TEXT(" Singleton = GetStaticStruct(%s, %s::StaticClass(), TEXT(\"%s\"));\r\n"), *SingletonName, NameLookupCPP->GetNameCPP(CastChecked<UStruct>(Struct->GetOuter())), *Struct->GetName());
|
|
}
|
|
else
|
|
{
|
|
FString PackageSingletonName = GetPackageSingletonName(CastChecked<UPackage>(Struct->GetOuter()));
|
|
GeneratedPackageCPP.Logf(TEXT(" extern %sclass UPackage* %s;\r\n"), *FriendApiString, *PackageSingletonName);
|
|
GeneratedPackageCPP.Logf(TEXT(" Singleton = GetStaticStruct(%s, %s, TEXT(\"%s\"));\r\n"), *SingletonName, *PackageSingletonName, *Struct->GetName());
|
|
}
|
|
|
|
GeneratedPackageCPP.Logf(TEXT(" }\r\n"));
|
|
GeneratedPackageCPP.Logf(TEXT(" return Singleton;\r\n"));
|
|
GeneratedPackageCPP.Logf(TEXT("}\r\n"));
|
|
GeneratedPackageCPP.Logf(TEXT("static FCompiledInDeferStruct Z_CompiledInDeferStruct_UScriptStruct_%s(%s::StaticStruct);\r\n"), NameLookupCPP->GetNameCPP(Struct), NameLookupCPP->GetNameCPP(Struct));
|
|
|
|
// Generate StaticRegisterNatives equivalent for structs without classes.
|
|
if (!Struct->GetOuter()->IsA(UStruct::StaticClass()))
|
|
{
|
|
const FString ShortPackageName = FPackageName::GetShortName(Struct->GetOuter()->GetName());
|
|
GeneratedPackageCPP.Logf(TEXT("static struct FScriptStruct_%s_StaticRegisterNatives%s\r\n"), *ShortPackageName, NameLookupCPP->GetNameCPP(Struct));
|
|
GeneratedPackageCPP.Logf(TEXT("{\r\n"));
|
|
GeneratedPackageCPP.Logf(TEXT("\tFScriptStruct_%s_StaticRegisterNatives%s()\r\n"), *ShortPackageName, NameLookupCPP->GetNameCPP(Struct));
|
|
GeneratedPackageCPP.Logf(TEXT("\t{\r\n"));
|
|
|
|
GeneratedPackageCPP.Logf( TEXT("\t\tUScriptStruct::DeferCppStructOps(FName(TEXT(\"%s\")),new UScriptStruct::TCppStructOps<%s%s>);\r\n"), *Struct->GetName(), Struct->GetPrefixCPP(), *Struct->GetName() );
|
|
|
|
GeneratedPackageCPP.Logf(TEXT("\t}\r\n"));
|
|
GeneratedPackageCPP.Logf(TEXT("} ScriptStruct_%s_StaticRegisterNatives%s;\r\n"), *ShortPackageName, NameLookupCPP->GetNameCPP(Struct));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FNativeClassHeaderGenerator::ExportGeneratedEnumsInitCode(const TArray<UEnum*>& Enums)
|
|
{
|
|
// reverse the order.
|
|
for (int32 i = Enums.Num() - 1; i >= 0; --i)
|
|
{
|
|
UEnum* Enum = Enums[i];
|
|
|
|
// Export Enum.
|
|
if (Enum->GetOuter()->IsA(UPackage::StaticClass()))
|
|
{
|
|
const FString FriendApiString = FString::Printf(TEXT("%s_API "), *API);
|
|
const FString StaticConstructionString = GetSingletonName(Enum);
|
|
|
|
FString SingletonName = StaticConstructionString.Replace(TEXT("()"), TEXT("")); // function address
|
|
|
|
GeneratedPackageCPP.Logf(TEXT("static class UEnum* %s_StaticEnum()\r\n"), *Enum->GetName());
|
|
GeneratedPackageCPP.Logf(TEXT("{\r\n"));
|
|
GeneratedPackageCPP.Logf(TEXT(" static class UEnum* Singleton = NULL;\r\n"));
|
|
GeneratedPackageCPP.Logf(TEXT(" if (!Singleton)\r\n"));
|
|
GeneratedPackageCPP.Logf(TEXT(" {\r\n"));
|
|
GeneratedPackageCPP.Logf(TEXT(" extern %sclass UEnum* %s;\r\n"), *FriendApiString, *StaticConstructionString);
|
|
|
|
FString PackageSingletonName = GetPackageSingletonName(CastChecked<UPackage>(Enum->GetOuter()));
|
|
GeneratedPackageCPP.Logf(TEXT(" extern %sclass UPackage* %s;\r\n"), *FriendApiString, *PackageSingletonName);
|
|
GeneratedPackageCPP.Logf(TEXT(" Singleton = GetStaticEnum(%s, %s, TEXT(\"%s\"));\r\n"), *SingletonName, *PackageSingletonName, *Enum->GetName());
|
|
|
|
GeneratedPackageCPP.Logf(TEXT(" }\r\n"));
|
|
GeneratedPackageCPP.Logf(TEXT(" return Singleton;\r\n"));
|
|
GeneratedPackageCPP.Logf(TEXT("}\r\n"));
|
|
GeneratedPackageCPP.Logf(TEXT("static FCompiledInDeferEnum Z_CompiledInDeferEnum_UEnum_%s(%s_StaticEnum);\r\n"), *Enum->GetName(), *Enum->GetName());
|
|
}
|
|
}
|
|
}
|
|
|
|
void FNativeClassHeaderGenerator::ExportMirrorsForNoexportStructs(const TArray<UScriptStruct*>& NativeStructs, int32 TextIndent, FStringOutputDevice& HeaderOutput)
|
|
{
|
|
// reverse the order.
|
|
for (int32 i = NativeStructs.Num() - 1; i >= 0; --i)
|
|
{
|
|
UScriptStruct* Struct = NativeStructs[i];
|
|
|
|
// Export struct.
|
|
HeaderOutput.Logf( TEXT("%sstruct %s"), FCString::Spc(TextIndent), NameLookupCPP->GetNameCPP( Struct ) );
|
|
if (Struct->GetSuperStruct() != NULL)
|
|
{
|
|
HeaderOutput.Logf(TEXT(" : public %s"), NameLookupCPP->GetNameCPP(Struct->GetSuperStruct()));
|
|
}
|
|
HeaderOutput.Logf( TEXT("\r\n%s{\r\n"), FCString::Spc(TextIndent) );
|
|
|
|
// Export the struct's CPP properties.
|
|
ExportProperties(Struct, TextIndent, /*bAccessSpecifiers=*/ false, &HeaderOutput);
|
|
|
|
HeaderOutput.Logf( TEXT("%s};\r\n"), FCString::Spc(TextIndent) );
|
|
|
|
HeaderOutput.Log( TEXT("\r\n") );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Exports the parameter struct declarations for the list of functions specified
|
|
*
|
|
* @param Function the function that (may) have parameters which need to be exported
|
|
* @return true if the structure generated is not completely empty
|
|
*/
|
|
bool FNativeClassHeaderGenerator::WillExportEventParms( UFunction* Function )
|
|
{
|
|
for( TFieldIterator<UProperty> It(Function); It && (It->PropertyFlags&CPF_Parm); ++It )
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void WriteEventFunctionPrologue(FStringOutputDevice& Output, int32 Indent, const FParmsAndReturnProperties& Parameters, const TCHAR* ClassName, const TCHAR* FunctionName)
|
|
{
|
|
// now the body - first we need to declare a struct which will hold the parameters for the event/delegate call
|
|
Output.Logf( TEXT("\r\n%s{\r\n"), FCString::Spc(Indent) );
|
|
|
|
// declare and zero-initialize the parameters and return value, if applicable
|
|
if (!Parameters.HasParms())
|
|
return;
|
|
|
|
FString EventStructName = GetEventStructParamsName(ClassName, FunctionName);
|
|
|
|
Output.Logf( TEXT("%s%s Parms;\r\n"), FCString::Spc(Indent + 4), *EventStructName );
|
|
|
|
// Declare a parameter struct for this event/delegate and assign the struct members using the values passed into the event/delegate call.
|
|
for (auto It = Parameters.Parms.CreateConstIterator(); It; ++It)
|
|
{
|
|
UProperty* Prop = *It;
|
|
|
|
const FString PropertyName = Prop->GetName();
|
|
if ( Prop->ArrayDim > 1 )
|
|
{
|
|
Output.Logf( TEXT("%sFMemory::Memcpy(Parms.%s,%s,sizeof(Parms.%s));\r\n"), FCString::Spc(Indent + 4), *PropertyName, *PropertyName, *PropertyName );
|
|
}
|
|
else
|
|
{
|
|
FString ValueAssignmentText = PropertyName;
|
|
if (Prop->IsA<UBoolProperty>())
|
|
{
|
|
ValueAssignmentText += TEXT(" ? true : false");
|
|
}
|
|
|
|
Output.Logf( TEXT("%sParms.%s=%s;\r\n"), FCString::Spc(Indent + 4), *PropertyName, *ValueAssignmentText );
|
|
}
|
|
}
|
|
}
|
|
|
|
void WriteEventFunctionEpilogue(FStringOutputDevice& Output, int32 Indent, const FParmsAndReturnProperties& Parameters, const TCHAR* ClassName, const TCHAR* FunctionName)
|
|
{
|
|
// Out parm copying.
|
|
for (auto It = Parameters.Parms.CreateConstIterator(); It; ++It)
|
|
{
|
|
UProperty* Prop = *It;
|
|
|
|
if (Prop->HasAnyPropertyFlags(CPF_OutParm) && (!Prop->HasAnyPropertyFlags(CPF_ConstParm) || Prop->IsA<UObjectPropertyBase>()))
|
|
{
|
|
const FString PropertyName = Prop->GetName();
|
|
if ( Prop->ArrayDim > 1 )
|
|
{
|
|
Output.Logf( TEXT("%sFMemory::Memcpy(&%s,&Parms.%s,sizeof(%s));\r\n"), FCString::Spc(Indent + 4), *PropertyName, *PropertyName, *PropertyName );
|
|
}
|
|
else
|
|
{
|
|
Output.Logf(TEXT("%s%s=Parms.%s;\r\n"), FCString::Spc(Indent + 4), *PropertyName, *PropertyName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return value.
|
|
if (Parameters.Return)
|
|
{
|
|
// Make sure uint32 -> bool is supported
|
|
bool bBoolProperty = Parameters.Return->IsA(UBoolProperty::StaticClass());
|
|
Output.Logf( TEXT("%sreturn %sParms.%s;\r\n"), FCString::Spc(Indent + 4), bBoolProperty ? TEXT("!!") : TEXT(""), *Parameters.Return->GetName() );
|
|
}
|
|
Output.Logf( TEXT("%s}\r\n"), FCString::Spc(Indent) );
|
|
}
|
|
|
|
/**
|
|
* Exports C++ type definitions for delegates
|
|
*
|
|
* @param DelegateFunctions the functions that have parameters which need to be exported
|
|
* @param bWrapperImplementationsOnly True if only function implementations for delegate wrappers should be exported
|
|
*/
|
|
void FNativeClassHeaderGenerator::ExportDelegateDefinitions( const TArray<UFunction*>& DelegateFunctions, const bool bWrapperImplementationsOnly )
|
|
{
|
|
FStringOutputDevice HeaderOutput;
|
|
if( bWrapperImplementationsOnly )
|
|
{
|
|
// Export parameters structs for all delegates. We'll need these to declare our delegate execution function.
|
|
ExportEventParms( DelegateFunctions, TEXT(""), 0, true, &HeaderOutput);
|
|
}
|
|
|
|
// find the class info for this class
|
|
FClassMetaData* ClassData = GScriptHelper->FindClassData(CurrentClass);
|
|
UClass* Class = CurrentClass;
|
|
const FString ClassName = Class->GetName();
|
|
|
|
for ( int32 i = DelegateFunctions.Num() - 1; i >= 0 ; i-- )
|
|
{
|
|
UFunction* Function = DelegateFunctions[i];
|
|
check( Function->HasAnyFunctionFlags( FUNC_Delegate ) );
|
|
|
|
const bool bIsMulticastDelegate = Function->HasAnyFunctionFlags( FUNC_MulticastDelegate );
|
|
|
|
// Unmangle the function name
|
|
const FString DelegateName = Function->GetName().LeftChop( FString( HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX ).Len() );
|
|
|
|
FFunctionData* CompilerInfo = ClassData->FindFunctionData(Function);
|
|
check(CompilerInfo);
|
|
|
|
FFuncInfo FunctionData = CompilerInfo->GetFunctionData();
|
|
|
|
if( bWrapperImplementationsOnly )
|
|
{
|
|
// Always export delegate wrapper functions as inline
|
|
FunctionData.FunctionExportFlags |= FUNCEXPORT_Inline;
|
|
}
|
|
|
|
// Add class name to beginning of function, to avoid collisions with other classes with the same delegate name in this scope
|
|
FString Delegate(TEXT("delegate"));
|
|
check(FunctionData.MarshallAndCallName.StartsWith(Delegate));
|
|
FString ShortName = *FunctionData.MarshallAndCallName + Delegate.Len();
|
|
FunctionData.MarshallAndCallName = FString::Printf( TEXT( "F%s_DelegateWrapper" ), *ShortName );
|
|
|
|
// Setup delegate parameter
|
|
const FString ExtraParam( FString::Printf( TEXT( "const %s& %s" ),
|
|
bIsMulticastDelegate ? TEXT( "FMulticastScriptDelegate" ) : TEXT( "FScriptDelegate" ),
|
|
*DelegateName ) );
|
|
|
|
// export the line that looks like: int32 Main(const FString& Parms)
|
|
ExportNativeFunctionHeader(FunctionData,HeaderOutput,EExportFunctionType::Event,EExportFunctionHeaderStyle::Declaration,*ExtraParam);
|
|
|
|
if( !bWrapperImplementationsOnly )
|
|
{
|
|
// Only exporting function prototype
|
|
HeaderOutput.Logf( TEXT( ";\r\n" ) );
|
|
}
|
|
else
|
|
{
|
|
auto Parameters = GetFunctionParmsAndReturn(FunctionData.FunctionReference);
|
|
|
|
WriteEventFunctionPrologue(HeaderOutput, 0, Parameters, *ClassName, *DelegateName);
|
|
{
|
|
const TCHAR* DelegateType = bIsMulticastDelegate ? TEXT( "ProcessMulticastDelegate" ) : TEXT( "ProcessDelegate" );
|
|
const TCHAR* DelegateArg = Parameters.HasParms() ? TEXT("&Parms") : TEXT("NULL");
|
|
HeaderOutput.Logf( TEXT("%s%s.%s<UObject>(%s);\r\n"), FCString::Spc(4), *DelegateName, DelegateType, DelegateArg );
|
|
}
|
|
WriteEventFunctionEpilogue(HeaderOutput, 0, Parameters, *ClassName, *DelegateName);
|
|
}
|
|
}
|
|
|
|
if (HeaderOutput.Len())
|
|
{
|
|
if( !bWrapperImplementationsOnly )
|
|
{
|
|
// these are at the top, so we can output directly
|
|
GeneratedHeaderText.Log(*HeaderOutput);
|
|
GeneratedHeaderText.Log(TEXT("\r\n\r\n"));
|
|
}
|
|
else
|
|
{
|
|
const TCHAR* Suffix = TEXT("_DELEGATES");
|
|
FString MacroName = FString(NameLookupCPP->GetNameCPP(CurrentClass)) + Suffix;
|
|
FString Macroized = Macroize(*MacroName, *HeaderOutput);
|
|
GeneratedHeaderText.Log(*Macroized);
|
|
PrologMacroCalls.Logf( TEXT("%s%s\r\n"), FCString::Spc(0), *MacroName);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Export a single .proto declaration, recursing as necessary for sub declarations
|
|
*
|
|
* @param Out output device
|
|
* @param MessageName name of the message in the declaration
|
|
* @param Properties array of parameters in the function definition
|
|
* @param PropertyFlags flags to filter property array against
|
|
* @param Ident starting indentation level
|
|
*/
|
|
void ExportProtoDeclaration(FOutputDevice& Out, const FString& MessageName, TFieldIterator<UProperty>& Properties, uint64 PropertyFlags, int32 Indent)
|
|
{
|
|
Out.Logf(TEXT("%smessage CMsg%sMessage\r\n"), FCString::Spc(Indent), *MessageName);
|
|
Out.Logf(TEXT("%s{\r\n"), FCString::Spc(Indent));
|
|
|
|
static TMap<FString, FString> ToProtoTypeMappings;
|
|
static bool bInitMapping = false;
|
|
if (!bInitMapping)
|
|
{
|
|
// Explicit type mappings
|
|
ToProtoTypeMappings.Add(TEXT("FString"), TEXT("string"));
|
|
ToProtoTypeMappings.Add(TEXT("int32"), TEXT("int32"));
|
|
ToProtoTypeMappings.Add(TEXT("int64"), TEXT("int64"));
|
|
ToProtoTypeMappings.Add(TEXT("uint8"), TEXT("bytes"));
|
|
ToProtoTypeMappings.Add(TEXT("bool"), TEXT("bool"));
|
|
ToProtoTypeMappings.Add(TEXT("double"), TEXT("double"));
|
|
ToProtoTypeMappings.Add(TEXT("float"), TEXT("float"));
|
|
bInitMapping = true;
|
|
}
|
|
|
|
int32 FieldIdx = 1;
|
|
for(; Properties && (Properties->PropertyFlags & PropertyFlags); ++Properties)
|
|
{
|
|
UProperty* Property = *Properties;
|
|
UClass* PropClass = Property->GetClass();
|
|
|
|
// Skip out and return paramaters
|
|
if ((Property->PropertyFlags & CPF_RepSkip) || (Property->PropertyFlags & CPF_ReturnParm))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// export the property type text (e.g. FString; int32; TArray, etc.)
|
|
FString TypeText, ExtendedTypeText;
|
|
TypeText = Property->GetCPPType(&ExtendedTypeText, CPPF_None);
|
|
if (PropClass != UInterfaceProperty::StaticClass() && PropClass != UObjectProperty::StaticClass())
|
|
{
|
|
bool bIsRepeated = false;
|
|
UArrayProperty* ArrayProperty = Cast<UArrayProperty>(Property);
|
|
if (ArrayProperty != NULL)
|
|
{
|
|
UClass* InnerPropClass = ArrayProperty->Inner->GetClass();
|
|
if (InnerPropClass != UInterfaceProperty::StaticClass() && InnerPropClass != UObjectProperty::StaticClass())
|
|
{
|
|
FString InnerExtendedTypeText;
|
|
FString InnerTypeText = ArrayProperty->Inner->GetCPPType(&InnerExtendedTypeText, CPPF_None);
|
|
TypeText = InnerTypeText;
|
|
ExtendedTypeText = InnerExtendedTypeText;
|
|
Property = ArrayProperty->Inner;
|
|
bIsRepeated = true;
|
|
}
|
|
else
|
|
{
|
|
FError::Throwf(TEXT("ExportProtoDeclaration - Unhandled property type '%s': %s"), *PropClass->GetName(), *Property->GetPathName());
|
|
}
|
|
}
|
|
else if(Property->ArrayDim != 1)
|
|
{
|
|
bIsRepeated = true;
|
|
}
|
|
|
|
FString VariableTypeName = FString::Printf(TEXT("%s%s"), *TypeText, *ExtendedTypeText);
|
|
FString* ProtoTypeName = NULL;
|
|
|
|
UStructProperty* StructProperty = Cast<UStructProperty>(Property);
|
|
if (StructProperty != NULL)
|
|
{
|
|
TFieldIterator<UProperty> StructIt(StructProperty->Struct);
|
|
ExportProtoDeclaration(Out, VariableTypeName, StructIt, CPF_AllFlags, Indent + 4);
|
|
VariableTypeName = FString::Printf(TEXT("CMsg%sMessage"), *VariableTypeName);
|
|
ProtoTypeName = &VariableTypeName;
|
|
}
|
|
else
|
|
{
|
|
ProtoTypeName = ToProtoTypeMappings.Find(VariableTypeName);
|
|
}
|
|
|
|
Out.Log(FCString::Spc(Indent + 4));
|
|
if (bIsRepeated)
|
|
{
|
|
Out.Log( TEXT("repeated "));
|
|
}
|
|
else
|
|
{
|
|
Out.Log( TEXT("optional "));
|
|
}
|
|
|
|
if (ProtoTypeName != NULL)
|
|
{
|
|
Out.Logf( TEXT("%s %s = %d;\r\n"), **ProtoTypeName, *Property->GetNameCPP(), FieldIdx);
|
|
FieldIdx++;
|
|
}
|
|
else
|
|
{
|
|
FError::Throwf(TEXT("ExportProtoDeclaration - Unhandled property mapping '%s': %s"), *PropClass->GetName(), *Property->GetPathName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FError::Throwf(TEXT("ExportProtoDeclaration - Unhandled property type '%s': %s"), *PropClass->GetName(), *Property->GetPathName());
|
|
}
|
|
}
|
|
|
|
Out.Logf( TEXT("%s}\r\n"), FCString::Spc(Indent) );
|
|
}
|
|
|
|
/**
|
|
* Generate a .proto message declaration for any functions marked as requiring one
|
|
*
|
|
* @param InCallbackFunctions array of functions for consideration to generate .proto definitions
|
|
* @param Indent starting indentation level
|
|
* @param Output optional output redirect
|
|
*/
|
|
void FNativeClassHeaderGenerator::ExportProtoMessage(const TArray<UFunction*>& InCallbackFunctions, int32 Indent, class FStringOutputDevice* Output)
|
|
{
|
|
// find the class info for this class
|
|
FClassMetaData* ClassData = GScriptHelper->FindClassData(CurrentClass);
|
|
|
|
// Parms struct definitions.
|
|
FStringOutputDevice HeaderOutput;
|
|
|
|
TArray<UFunction*> CallbackFunctions = InCallbackFunctions;
|
|
CallbackFunctions.Sort();
|
|
|
|
for (int32 Index = 0; Index < CallbackFunctions.Num(); Index++)
|
|
{
|
|
UFunction* Function = CallbackFunctions[Index];
|
|
FFunctionData* CompilerInfo = ClassData->FindFunctionData(Function);
|
|
check(CompilerInfo);
|
|
const FFuncInfo& FunctionData = CompilerInfo->GetFunctionData();
|
|
if (FunctionData.FunctionExportFlags & FUNCEXPORT_NeedsProto)
|
|
{
|
|
if (WillExportEventParms(Function) && !Function->HasAnyFunctionFlags(FUNC_Delegate))
|
|
{
|
|
FString FunctionName = Function->GetName();
|
|
TFieldIterator<UProperty> CommentIt(Function);
|
|
FString ParameterList;
|
|
for(; CommentIt && (CommentIt->PropertyFlags & CPF_Parm); ++CommentIt)
|
|
{
|
|
UProperty* Param = *CommentIt;
|
|
FString TypeText, ExtendedTypeText;
|
|
TypeText = Param->GetCPPType(&ExtendedTypeText, CPPF_None);
|
|
FString ParamName = FString::Printf(TEXT("%s%s %s"), *TypeText, *ExtendedTypeText, *Param->GetName());
|
|
|
|
// add this property to the parameter list string
|
|
if (ParameterList.Len())
|
|
{
|
|
ParameterList += TCHAR(',');
|
|
}
|
|
|
|
ParameterList += ParamName;
|
|
}
|
|
|
|
HeaderOutput.Logf(TEXT("// %s%s(%s)\r\n"), FCString::Spc(Indent), *FunctionName, *ParameterList);
|
|
|
|
TFieldIterator<UProperty> ParamIt(Function);
|
|
ExportProtoDeclaration(HeaderOutput, FunctionName, ParamIt, CPF_Parm, Indent);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Output)
|
|
{
|
|
GeneratedProtoText.Log(*HeaderOutput);
|
|
}
|
|
else
|
|
{
|
|
Output->Log(HeaderOutput);
|
|
}
|
|
}
|
|
|
|
// Java uses different coding standards for capitalization
|
|
static FString FixJavaName(const FString &StringIn)
|
|
{
|
|
FString FixedString = StringIn;
|
|
|
|
FixedString[0] = FChar::ToLower(FixedString[0]); // java classes/variable start lower case
|
|
FixedString.ReplaceInline(TEXT("ID"), TEXT("Id"), ESearchCase::CaseSensitive); // Id is standard instead of ID, some of our fnames use ID
|
|
|
|
return FixedString;
|
|
}
|
|
|
|
/**
|
|
* Export a single .java declaration, recursing as necessary for sub declarations
|
|
*
|
|
* @param Out output device
|
|
* @param MessageName name of the message in the declaration
|
|
* @param Properties array of parameters in the function definition
|
|
* @param PropertyFlags flags to filter property array against
|
|
* @param Ident starting indentation level
|
|
*/
|
|
void ExportMCPDeclaration(FOutputDevice& Out, const FString& MessageName, TFieldIterator<UProperty>& Properties, uint64 PropertyFlags, int32 Indent)
|
|
{
|
|
Out.Logf(TEXT("%spublic class %sCommand extends ProfileCommand\r\n"), FCString::Spc(Indent), *MessageName);
|
|
Out.Logf(TEXT("%s{\r\n"), FCString::Spc(Indent));
|
|
|
|
static TMap<FString, FString> ToMCPTypeMappings;
|
|
static TMap<FString, FString> ToAnnotationMappings;
|
|
static bool bInitMapping = false;
|
|
if (!bInitMapping)
|
|
{
|
|
// Explicit type mappings
|
|
ToMCPTypeMappings.Add(TEXT("FString"), TEXT("String"));
|
|
ToMCPTypeMappings.Add(TEXT("int32"), TEXT("int"));
|
|
ToMCPTypeMappings.Add(TEXT("int64"), TEXT("int"));
|
|
ToMCPTypeMappings.Add(TEXT("uint8"), TEXT("byte"));
|
|
ToMCPTypeMappings.Add(TEXT("bool"), TEXT("boolean"));
|
|
ToMCPTypeMappings.Add(TEXT("double"), TEXT("double"));
|
|
ToMCPTypeMappings.Add(TEXT("float"), TEXT("float"));
|
|
|
|
ToAnnotationMappings.Add(TEXT("FString"), TEXT("@NotBlankOrNull"));
|
|
|
|
bInitMapping = true;
|
|
}
|
|
|
|
FString ConstructorParams;
|
|
FString ConstructorText;
|
|
|
|
for(; Properties && (Properties->PropertyFlags & PropertyFlags); ++Properties)
|
|
{
|
|
UProperty* Property = *Properties;
|
|
UClass* PropClass = Property->GetClass();
|
|
|
|
// Skip out and return paramaters
|
|
if ((Property->PropertyFlags & CPF_RepSkip) || (Property->PropertyFlags & CPF_ReturnParm))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// export the property type text (e.g. FString; int32; TArray, etc.)
|
|
FString TypeText, ExtendedTypeText;
|
|
TypeText = Property->GetCPPType(&ExtendedTypeText, CPPF_None);
|
|
if (PropClass != UInterfaceProperty::StaticClass() && PropClass != UObjectProperty::StaticClass())
|
|
{
|
|
// TODO Implement arrays
|
|
UArrayProperty* ArrayProperty = Cast<UArrayProperty>(Property);
|
|
if (ArrayProperty != NULL)
|
|
{
|
|
// skip array generation for Java, this should result in a List<TYPE> declaration, but we can do this by hand for now.
|
|
continue;
|
|
}
|
|
/*bool bIsRepeated = false;
|
|
UArrayProperty* ArrayProperty = Cast<UArrayProperty>(Property);
|
|
if (ArrayProperty != NULL)
|
|
{
|
|
UClass* InnerPropClass = ArrayProperty->Inner->GetClass();
|
|
if (InnerPropClass != UInterfaceProperty::StaticClass() && InnerPropClass != UObjectProperty::StaticClass())
|
|
{
|
|
FString InnerExtendedTypeText;
|
|
FString InnerTypeText = ArrayProperty->Inner->GetCPPType(&InnerExtendedTypeText, CPPF_None);
|
|
TypeText = InnerTypeText;
|
|
ExtendedTypeText = InnerExtendedTypeText;
|
|
Property = ArrayProperty->Inner;
|
|
bIsRepeated = true;
|
|
}
|
|
else
|
|
{
|
|
FError::Throwf(TEXT("ExportMCPDeclaration - Unhandled property type '%s': %s"), *PropClass->GetName(), *Property->GetPathName());
|
|
}
|
|
}
|
|
else if(Property->ArrayDim != 1)
|
|
{
|
|
bIsRepeated = true;
|
|
}*/
|
|
|
|
FString VariableTypeName = FString::Printf(TEXT("%s%s"), *TypeText, *ExtendedTypeText);
|
|
FString PropertyName = FixJavaName(Property->GetNameCPP());
|
|
FString* MCPTypeName = NULL;
|
|
FString* AnnotationName = NULL;
|
|
|
|
|
|
UStructProperty* StructProperty = Cast<UStructProperty>(Property);
|
|
if (StructProperty != NULL)
|
|
{
|
|
// TODO Implement structs
|
|
/* TFieldIterator<UProperty> StructIt(StructProperty->Struct);
|
|
ExportMCPDeclaration(Out, VariableTypeName, StructIt, CPF_AllFlags, Indent + 4);
|
|
VariableTypeName = FString::Printf(TEXT("CMsg%sMessage"), *VariableTypeName);
|
|
MCPTypeName = &VariableTypeName;*/
|
|
}
|
|
else
|
|
{
|
|
MCPTypeName = ToMCPTypeMappings.Find(VariableTypeName);
|
|
AnnotationName = ToAnnotationMappings.Find(VariableTypeName);
|
|
}
|
|
|
|
if (AnnotationName != NULL && !AnnotationName->IsEmpty())
|
|
{
|
|
Out.Log(FCString::Spc(Indent + 4));
|
|
Out.Logf(TEXT("%s\r\n"), **AnnotationName);
|
|
}
|
|
|
|
if (MCPTypeName != NULL)
|
|
{
|
|
Out.Log(FCString::Spc(Indent + 4));
|
|
Out.Logf(TEXT("private %s %s;\r\n"), **MCPTypeName, *PropertyName);
|
|
|
|
ConstructorParams += FString::Printf(TEXT(", %s %s"), **MCPTypeName, *PropertyName);
|
|
ConstructorText += FString::Printf(TEXT("%sthis.%s = %s;\r\n"), FCString::Spc(Indent + 8), *PropertyName, *PropertyName);
|
|
}
|
|
else
|
|
{
|
|
FError::Throwf(TEXT("ExportMCPDeclaration - Unhandled property mapping '%s': %s"), *PropClass->GetName(), *Property->GetPathName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FError::Throwf(TEXT("ExportMCPDeclaration - Unhandled property type '%s': %s"), *PropClass->GetName(), *Property->GetPathName());
|
|
}
|
|
}
|
|
|
|
Out.Logf(TEXT("\r\n%spublic %sCommand(String epicId, String profileId%s)\r\n"), FCString::Spc(Indent + 4), *MessageName, *ConstructorParams);
|
|
Out.Logf(TEXT("%s{\r\n"), FCString::Spc(Indent + 4));
|
|
Out.Logf(TEXT("%ssuper(epicId, profileId);\r\n"), FCString::Spc(Indent + 8));
|
|
Out.Logf(TEXT("%s"), *ConstructorText);
|
|
Out.Logf(TEXT("%s}\r\n"), FCString::Spc(Indent + 4));
|
|
|
|
Out.Logf(TEXT("\r\n%s@Override\r\n"), FCString::Spc(Indent + 4));
|
|
Out.Logf(TEXT("%sprotected void execute(@Name(\"profile\") @NotNull ProfileEx profile)\r\n"), FCString::Spc(Indent + 4));
|
|
Out.Logf(TEXT("%s{\r\n"), FCString::Spc(Indent + 4));
|
|
Out.Logf(TEXT("%s}\r\n"), FCString::Spc(Indent + 4));
|
|
|
|
Out.Logf( TEXT("%s}\r\n"), FCString::Spc(Indent) );
|
|
}
|
|
|
|
/**
|
|
* Generate a .MCP message declaration for any functions marked as requiring one
|
|
*
|
|
* @param InCallbackFunctions array of functions for consideration to generate .proto definitions
|
|
* @param Indent starting indentation level
|
|
* @param Output optional output redirect
|
|
*/
|
|
void FNativeClassHeaderGenerator::ExportMCPMessage(const TArray<UFunction*>& InCallbackFunctions, int32 Indent, class FStringOutputDevice* Output)
|
|
{
|
|
// find the class info for this class
|
|
FClassMetaData* ClassData = GScriptHelper->FindClassData(CurrentClass);
|
|
|
|
// Parms struct definitions.
|
|
FStringOutputDevice HeaderOutput;
|
|
|
|
TArray<UFunction*> CallbackFunctions = InCallbackFunctions;
|
|
CallbackFunctions.Sort();
|
|
|
|
for (int32 Index = 0; Index < CallbackFunctions.Num(); Index++)
|
|
{
|
|
UFunction* Function = CallbackFunctions[Index];
|
|
FFunctionData* CompilerInfo = ClassData->FindFunctionData(Function);
|
|
check(CompilerInfo);
|
|
const FFuncInfo& FunctionData = CompilerInfo->GetFunctionData();
|
|
if (FunctionData.FunctionExportFlags & FUNCEXPORT_NeedsMCP)
|
|
{
|
|
if (WillExportEventParms(Function) && !Function->HasAnyFunctionFlags(FUNC_Delegate))
|
|
{
|
|
FString FunctionName = Function->GetName();
|
|
TFieldIterator<UProperty> CommentIt(Function);
|
|
FString ParameterList;
|
|
for(; CommentIt && (CommentIt->PropertyFlags & CPF_Parm); ++CommentIt)
|
|
{
|
|
UProperty* Param = *CommentIt;
|
|
FString TypeText, ExtendedTypeText;
|
|
TypeText = Param->GetCPPType(&ExtendedTypeText, CPPF_None);
|
|
FString ParamName = FString::Printf(TEXT("%s%s %s"), *TypeText, *ExtendedTypeText, *Param->GetName());
|
|
|
|
// add this property to the parameter list string
|
|
if (ParameterList.Len())
|
|
{
|
|
ParameterList += TCHAR(',');
|
|
}
|
|
|
|
ParameterList += ParamName;
|
|
}
|
|
|
|
HeaderOutput.Logf(TEXT("// %s%s(%s)\r\n"), FCString::Spc(Indent), *FunctionName, *ParameterList);
|
|
|
|
TFieldIterator<UProperty> ParamIt(Function);
|
|
ExportMCPDeclaration(HeaderOutput, FunctionName, ParamIt, CPF_Parm, Indent);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Output)
|
|
{
|
|
GeneratedMCPText.Log(*HeaderOutput);
|
|
}
|
|
else
|
|
{
|
|
Output->Log(HeaderOutput);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Exports the parameter struct declarations for the list of functions specified
|
|
*
|
|
* @param CallbackFunctions the functions that have parameters which need to be exported
|
|
*/
|
|
void FNativeClassHeaderGenerator::ExportEventParms( const TArray<UFunction*>& InCallbackFunctions, const TCHAR* MacroSuffix, int32 Indent, bool bOutputConstructor, class FStringOutputDevice* Output )
|
|
{
|
|
// Parms struct definitions.
|
|
FStringOutputDevice HeaderOutput;
|
|
|
|
TArray<UFunction*> CallbackFunctions = InCallbackFunctions;
|
|
CallbackFunctions.Sort();
|
|
|
|
for ( int32 Index = 0; Index < CallbackFunctions.Num(); Index++ )
|
|
{
|
|
UFunction* Function = CallbackFunctions[Index];
|
|
if (!WillExportEventParms(Function))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FString FunctionName = Function->GetName();
|
|
if( Function->HasAnyFunctionFlags( FUNC_Delegate ) )
|
|
{
|
|
FunctionName = FunctionName.LeftChop( FString( HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX ).Len() );
|
|
}
|
|
|
|
FString EventParmStructName = GetEventStructParamsName(*Function->GetOwnerClass()->GetName(), *FunctionName);
|
|
HeaderOutput.Logf( TEXT("%sstruct %s\r\n"), FCString::Spc(Indent), *EventParmStructName);
|
|
HeaderOutput.Logf( TEXT("%s{\r\n"), FCString::Spc(Indent) );
|
|
|
|
for( TFieldIterator<UProperty> It(Function); It && (It->PropertyFlags&CPF_Parm); ++It )
|
|
{
|
|
UProperty* Prop = *It;
|
|
|
|
FStringOutputDevice PropertyText;
|
|
PropertyText.Log( FCString::Spc(4 + Indent) );
|
|
|
|
bool bEmitConst = Prop->HasAnyPropertyFlags(CPF_ConstParm) && Prop->IsA<UObjectProperty>();
|
|
|
|
//@TODO: UCREMOVAL: This is awful code duplication to avoid a double-const
|
|
{
|
|
// export 'const' for parameters
|
|
const bool bIsConstParam = (Prop->IsA(UInterfaceProperty::StaticClass()) && !Prop->HasAllPropertyFlags(CPF_OutParm)); //@TODO: This should be const once that flag exists
|
|
const bool bIsOnConstClass = (Prop->IsA(UObjectProperty::StaticClass()) && ((UObjectProperty*)Prop)->PropertyClass != NULL && ((UObjectProperty*)Prop)->PropertyClass->HasAnyClassFlags(CLASS_Const));
|
|
|
|
if (bIsConstParam || bIsOnConstClass)
|
|
{
|
|
bEmitConst = false; // ExportCppDeclaration will do it for us
|
|
}
|
|
}
|
|
|
|
if (bEmitConst)
|
|
{
|
|
PropertyText.Logf(TEXT("const "));
|
|
}
|
|
|
|
const FString* Dim = GArrayDimensions.Find(Prop);
|
|
Prop->ExportCppDeclaration(PropertyText, EExportedDeclaration::Local, Dim ? **Dim : NULL);
|
|
ApplyAlternatePropertyExportText(Prop, PropertyText);
|
|
|
|
PropertyText.Log( TEXT(";\r\n") );
|
|
HeaderOutput += *PropertyText;
|
|
|
|
}
|
|
// constructor must initialize the return property if it needs it
|
|
UProperty* Prop = Function->GetReturnProperty();
|
|
if (Prop && bOutputConstructor)
|
|
{
|
|
FStringOutputDevice InitializationAr;
|
|
|
|
UStructProperty* InnerStruct = Cast<UStructProperty>(Prop);
|
|
bool bNeedsOutput = true;
|
|
if (InnerStruct)
|
|
{
|
|
bNeedsOutput = InnerStruct->HasNoOpConstructor();
|
|
}
|
|
else if (
|
|
Cast<UNameProperty>(Prop) ||
|
|
Cast<UDelegateProperty>(Prop) ||
|
|
Cast<UMulticastDelegateProperty>(Prop) ||
|
|
Cast<UStrProperty>(Prop) ||
|
|
Cast<UTextProperty>(Prop) ||
|
|
Cast<UArrayProperty>(Prop) ||
|
|
Cast<UInterfaceProperty>(Prop)
|
|
)
|
|
{
|
|
bNeedsOutput = false;
|
|
}
|
|
if (bNeedsOutput)
|
|
{
|
|
check(Prop->ArrayDim == 1); // can't return arrays
|
|
HeaderOutput.Logf(TEXT("\r\n%s/** Constructor, intializes return property only **/\r\n"), FCString::Spc(4 + Indent));
|
|
HeaderOutput.Logf(TEXT("%s%s()\r\n"), FCString::Spc(4 + Indent), *EventParmStructName);
|
|
HeaderOutput.Logf(TEXT("%s%s %s(%s)\r\n"), FCString::Spc(8 + Indent),TEXT(":") , *Prop->GetName(), *GetNullParameterValue(Prop,false,true));
|
|
HeaderOutput.Logf(TEXT("%s{\r\n"), FCString::Spc(4 + Indent));
|
|
HeaderOutput.Logf(TEXT("%s}\r\n"), FCString::Spc(4 + Indent));
|
|
}
|
|
}
|
|
HeaderOutput.Logf( TEXT("%s};\r\n"), FCString::Spc(Indent) );
|
|
}
|
|
|
|
if (!Output)
|
|
{
|
|
const TCHAR* Suffix = TEXT("_EVENTPARMS");
|
|
FString MacroName = FString(NameLookupCPP->GetNameCPP(CurrentClass)) + Suffix + MacroSuffix;
|
|
FString Macroized = Macroize(*MacroName, *HeaderOutput);
|
|
GeneratedHeaderText.Log(*Macroized);
|
|
PrologMacroCalls.Logf( TEXT("%s%s\r\n"), FCString::Spc(Indent), *MacroName);
|
|
}
|
|
else
|
|
{
|
|
Output->Log(HeaderOutput);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the intrinsic null value for this property
|
|
*
|
|
* @param Prop the property to get the null value for
|
|
* @param bMacroContext true when exporting the P_GET* macro, false when exporting the friendly C++ function header
|
|
*
|
|
* @return the intrinsic null value for the property (0 for ints, TEXT("") for strings, etc.)
|
|
*/
|
|
FString FNativeClassHeaderGenerator::GetNullParameterValue( UProperty* Prop, bool bMacroContext, bool bInitializer/*=false*/ )
|
|
{
|
|
UClass* PropClass = Prop->GetClass();
|
|
UObjectPropertyBase* ObjectProperty = Cast<UObjectPropertyBase>(Prop);
|
|
if (PropClass == UByteProperty::StaticClass()
|
|
|| PropClass == UIntProperty::StaticClass()
|
|
|| PropClass == UBoolProperty::StaticClass()
|
|
|| PropClass == UFloatProperty::StaticClass()
|
|
|| PropClass == UDoubleProperty::StaticClass())
|
|
{
|
|
// if we have a BoolProperty then set it to be false instead of 0
|
|
if( PropClass == UBoolProperty::StaticClass() )
|
|
{
|
|
return TEXT("false");
|
|
}
|
|
|
|
return TEXT("0");
|
|
}
|
|
else if ( PropClass == UNameProperty::StaticClass() )
|
|
{
|
|
return TEXT("NAME_None");
|
|
}
|
|
else if ( PropClass == UStrProperty::StaticClass() )
|
|
{
|
|
return TEXT("TEXT(\"\")");
|
|
}
|
|
else if ( PropClass == UTextProperty::StaticClass() )
|
|
{
|
|
return TEXT("FText::GetEmpty()");
|
|
}
|
|
else if ( PropClass == UArrayProperty::StaticClass()
|
|
|| PropClass == UDelegateProperty::StaticClass()
|
|
|| PropClass == UMulticastDelegateProperty::StaticClass() )
|
|
{
|
|
FString Type, ExtendedType;
|
|
Type = Prop->GetCPPType(&ExtendedType,CPPF_OptionalValue);
|
|
return Type + ExtendedType + TEXT("()");
|
|
}
|
|
else if ( PropClass == UStructProperty::StaticClass() )
|
|
{
|
|
bool bHasNoOpConstuctor = CastChecked<UStructProperty>(Prop)->HasNoOpConstructor();
|
|
if (bInitializer && bHasNoOpConstuctor)
|
|
{
|
|
return TEXT("ForceInit");
|
|
}
|
|
|
|
FString Type, ExtendedType;
|
|
Type = Prop->GetCPPType(&ExtendedType,CPPF_OptionalValue);
|
|
return Type + ExtendedType + (bHasNoOpConstuctor ? TEXT("(ForceInit)") : TEXT("()"));
|
|
}
|
|
else if (ObjectProperty)
|
|
{
|
|
return TEXT("NULL");
|
|
}
|
|
else if ( PropClass == UInterfaceProperty::StaticClass() )
|
|
{
|
|
return TEXT("NULL");
|
|
}
|
|
|
|
UE_LOG(LogCompile, Fatal,TEXT("GetNullParameterValue - Unhandled property type '%s': %s"), *PropClass->GetName(), *Prop->GetPathName());
|
|
return TEXT("");
|
|
}
|
|
|
|
void FNativeClassHeaderGenerator::ExportNativeFunctionHeader( const FFuncInfo& FunctionData, FStringOutputDevice& HeaderOutput, EExportFunctionType::Type FunctionType, EExportFunctionHeaderStyle::Type FunctionHeaderStyle, const TCHAR* ExtraParam )
|
|
{
|
|
UFunction* Function = FunctionData.FunctionReference;
|
|
|
|
const bool bIsDelegate = Function->HasAnyFunctionFlags( FUNC_Delegate );
|
|
const bool bIsInterface = !bIsDelegate && Function->GetOwnerClass()->HasAnyClassFlags(CLASS_Interface);
|
|
const bool bIsK2Override = Function->HasAnyFunctionFlags( FUNC_BlueprintEvent );
|
|
|
|
HeaderOutput.Log( FCString::Spc( bIsDelegate ? 0 : 4) );
|
|
|
|
if (FunctionHeaderStyle == EExportFunctionHeaderStyle::Declaration)
|
|
{
|
|
// cpp implementation of functions never have these appendages
|
|
|
|
// If the function was marked as 'RequiredAPI', then add the *_API macro prefix. Note that if the class itself
|
|
// was marked 'RequiredAPI', this is not needed as C++ will exports all methods automatically.
|
|
if( !Function->GetOwnerClass()->HasAnyClassFlags( CLASS_RequiredAPI ) &&
|
|
( FunctionData.FunctionExportFlags & FUNCEXPORT_RequiredAPI ) )
|
|
{
|
|
HeaderOutput.Log( FString::Printf( TEXT( "%s_API " ), *API ) );
|
|
}
|
|
|
|
if(FunctionType == EExportFunctionType::Interface)
|
|
{
|
|
HeaderOutput.Log(TEXT("static "));
|
|
}
|
|
else if (bIsK2Override)
|
|
{
|
|
HeaderOutput.Log(TEXT("virtual "));
|
|
}
|
|
// if the owning class is an interface class
|
|
else if ( bIsInterface )
|
|
{
|
|
HeaderOutput.Log(TEXT("virtual "));
|
|
}
|
|
// this is not an event, the function is not a static function and the function is not marked final
|
|
else if ( FunctionType != EExportFunctionType::Event && !Function->HasAnyFunctionFlags(FUNC_Static) && !(FunctionData.FunctionExportFlags & FUNCEXPORT_Final) )
|
|
{
|
|
HeaderOutput.Log(TEXT("virtual "));
|
|
}
|
|
else if( FunctionData.FunctionExportFlags & FUNCEXPORT_Inline )
|
|
{
|
|
HeaderOutput.Log(TEXT("inline "));
|
|
}
|
|
}
|
|
|
|
if (auto Return = Function->GetReturnProperty())
|
|
{
|
|
FString ExtendedReturnType;
|
|
FString ReturnType = Return->GetCPPType(&ExtendedReturnType, (FunctionHeaderStyle == EExportFunctionHeaderStyle::Definition && (FunctionType != EExportFunctionType::Interface) ? CPPF_Implementation : 0) | CPPF_ArgumentOrReturnValue);
|
|
FStringOutputDevice ReplacementText(*ReturnType);
|
|
ApplyAlternatePropertyExportText(Return, ReplacementText);
|
|
HeaderOutput.Logf(TEXT("%s%s"), *ReplacementText, *ExtendedReturnType);
|
|
}
|
|
else
|
|
{
|
|
HeaderOutput.Log( TEXT("void") );
|
|
}
|
|
|
|
FString FunctionName;
|
|
if (FunctionHeaderStyle == EExportFunctionHeaderStyle::Definition)
|
|
{
|
|
FunctionName = FString(NameLookupCPP->GetNameCPP(CastChecked<UClass>(Function->GetOuter()), bIsInterface || FunctionType == EExportFunctionType::Interface)) + TEXT("::");
|
|
}
|
|
|
|
if (FunctionType == EExportFunctionType::Interface)
|
|
{
|
|
FunctionName += FString::Printf(TEXT("Execute_%s"), *Function->GetName());
|
|
}
|
|
else if (FunctionType == EExportFunctionType::Event)
|
|
{
|
|
FunctionName += FunctionData.MarshallAndCallName;
|
|
}
|
|
else
|
|
{
|
|
FunctionName += FunctionData.CppImplName;
|
|
}
|
|
|
|
HeaderOutput.Logf( TEXT(" %s("), *FunctionName);
|
|
|
|
int32 ParmCount=0;
|
|
|
|
// Emit extra parameter if we have one
|
|
if( ExtraParam )
|
|
{
|
|
HeaderOutput += ExtraParam;
|
|
++ParmCount;
|
|
}
|
|
|
|
for( TFieldIterator<UProperty> It(Function); It && (It->PropertyFlags&(CPF_Parm|CPF_ReturnParm))==CPF_Parm; ++It )
|
|
{
|
|
UProperty* Property = *It;
|
|
|
|
if( ParmCount++ )
|
|
{
|
|
HeaderOutput.Log(TEXT(", "));
|
|
}
|
|
|
|
FStringOutputDevice PropertyText;
|
|
|
|
const FString* Dim = GArrayDimensions.Find(Property);
|
|
Property->ExportCppDeclaration( PropertyText, EExportedDeclaration::Parameter, Dim ? **Dim : NULL );
|
|
ApplyAlternatePropertyExportText(Property, PropertyText);
|
|
|
|
HeaderOutput += PropertyText;
|
|
}
|
|
|
|
HeaderOutput.Log( TEXT(")") );
|
|
if (FunctionType != EExportFunctionType::Interface)
|
|
{
|
|
if (!bIsDelegate && Function->HasAllFunctionFlags(FUNC_Const))
|
|
{
|
|
HeaderOutput.Log( TEXT(" const") );
|
|
}
|
|
|
|
if (bIsInterface && FunctionHeaderStyle == EExportFunctionHeaderStyle::Declaration)
|
|
{
|
|
// all methods in interface classes are pure virtuals
|
|
HeaderOutput.Log(TEXT("=0"));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Export the actual internals to a standard thunk function
|
|
*
|
|
* @param RPCWrappers output device for writing
|
|
* @param FunctionData function data for the current function
|
|
* @param Parameters list of parameters in the function
|
|
* @param Return return parameter for the function
|
|
*/
|
|
void FNativeClassHeaderGenerator::ExportFunctionThunk(FStringOutputDevice& RPCWrappers, const FFuncInfo& FunctionData, const TArray<UProperty*>& Parameters, UProperty* Return)
|
|
{
|
|
// export the GET macro for this parameter
|
|
FString ParameterList;
|
|
for ( int32 ParameterIndex = 0; ParameterIndex < Parameters.Num(); ParameterIndex++ )
|
|
{
|
|
UProperty* Param = Parameters[ParameterIndex];
|
|
|
|
FString EvalBaseText = TEXT("P_GET_"); // e.g. P_GET_STR
|
|
FString EvalModifierText; // e.g. _REF
|
|
FString EvalParameterText; // e.g. (UObject*,NULL)
|
|
|
|
FString TypeText;
|
|
if ( Param->ArrayDim > 1 )
|
|
{
|
|
EvalBaseText += TEXT("ARRAY");
|
|
TypeText = Param->GetCPPType();
|
|
}
|
|
else
|
|
{
|
|
EvalBaseText += Param->GetCPPMacroType(TypeText);
|
|
}
|
|
FStringOutputDevice ReplacementText(*TypeText);
|
|
ApplyAlternatePropertyExportText(Param, ReplacementText);
|
|
TypeText = ReplacementText;
|
|
|
|
FString DefaultValueText;
|
|
FString ParamPrefix;
|
|
|
|
// if this property is an out parm, add the REF tag
|
|
if (Param->PropertyFlags & CPF_OutParm)
|
|
{
|
|
EvalModifierText += TEXT("_REF");
|
|
ParamPrefix = TEXT("Out_");
|
|
}
|
|
|
|
// if this property requires a specialization, add a comma to the type name so we can print it out easily
|
|
if ( TypeText != TEXT("") )
|
|
{
|
|
TypeText += TCHAR(',');
|
|
}
|
|
|
|
FString ParamName = ParamPrefix + Param->GetName();
|
|
|
|
EvalParameterText = FString::Printf(TEXT("(%s%s%s)"), *TypeText, *ParamName, *DefaultValueText);
|
|
|
|
RPCWrappers.Logf(TEXT("%s%s%s%s;%s"), FCString::Spc(8), *EvalBaseText, *EvalModifierText, *EvalParameterText, LINE_TERMINATOR);
|
|
|
|
// add this property to the parameter list string
|
|
if ( ParameterList.Len() )
|
|
{
|
|
ParameterList += TCHAR(',');
|
|
}
|
|
|
|
{
|
|
UDelegateProperty* DelegateProp = Cast< UDelegateProperty >( Param );
|
|
if( DelegateProp != NULL )
|
|
{
|
|
// For delegates, add an explicit conversion to the specific type of delegate before passing it along
|
|
const FString FunctionName = DelegateProp->SignatureFunction->GetName().LeftChop( FString( HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX ).Len() );
|
|
const FString CPPDelegateName = FString( TEXT("F" ) ) + FunctionName;
|
|
ParamName = FString::Printf( TEXT( "%s(%s)" ), *CPPDelegateName, *ParamName );
|
|
}
|
|
}
|
|
|
|
{
|
|
UMulticastDelegateProperty* MulticastDelegateProp = Cast< UMulticastDelegateProperty >( Param );
|
|
if( MulticastDelegateProp != NULL )
|
|
{
|
|
// For delegates, add an explicit conversion to the specific type of delegate before passing it along
|
|
const FString FunctionName = MulticastDelegateProp->SignatureFunction->GetName().LeftChop( FString( HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX ).Len() );
|
|
const FString CPPDelegateName = FString( TEXT("F" ) ) + FunctionName;
|
|
ParamName = FString::Printf( TEXT( "%s(%s)" ), *CPPDelegateName, *ParamName );
|
|
}
|
|
}
|
|
|
|
UByteProperty* ByteProp = Cast< UByteProperty >( Param );
|
|
if ((ByteProp != NULL) && (ByteProp->Enum != NULL))
|
|
{
|
|
UEnum* Enum = ByteProp->Enum;
|
|
|
|
FString FullyQualifiedEnumName = Enum->GetName();
|
|
if (!Enum->ActualEnumNameInsideNamespace.IsEmpty())
|
|
{
|
|
FullyQualifiedEnumName += TEXT("::");
|
|
FullyQualifiedEnumName += Enum->ActualEnumNameInsideNamespace;
|
|
}
|
|
|
|
// For enums, add an explicit conversion
|
|
if (!(ByteProp->PropertyFlags & CPF_OutParm))
|
|
{
|
|
ParamName = FString::Printf( TEXT( "%s(%s)" ), *FullyQualifiedEnumName, *ParamName );
|
|
}
|
|
else
|
|
{
|
|
ParamName = FString::Printf( TEXT( "(TEnumAsByte<%s>&)(%s)" ), *FullyQualifiedEnumName, *ParamName );
|
|
}
|
|
}
|
|
|
|
ParameterList += ParamName;
|
|
}
|
|
|
|
RPCWrappers.Logf(TEXT("%sP_FINISH;%s"), FCString::Spc(8), LINE_TERMINATOR);
|
|
|
|
// Call the validate function if there is one
|
|
if ( !( FunctionData.FunctionExportFlags & FUNCEXPORT_CppStatic ) && ( FunctionData.FunctionFlags & FUNC_NetValidate ) )
|
|
{
|
|
// Call the validate function, and if it fails, return (which will NOT execute the _Implementation function below)
|
|
// NOTE - We don't need to set "Result", since RPC calls don't return any values...
|
|
RPCWrappers.Logf(TEXT("%sif (!this->%s(%s))%s"), FCString::Spc(8), *FunctionData.CppValidationImplName, *ParameterList, LINE_TERMINATOR);
|
|
RPCWrappers.Logf(TEXT("%s{%s"), FCString::Spc(8), LINE_TERMINATOR);
|
|
RPCWrappers.Logf(TEXT("%sRPC_ValidateFailed(TEXT(\"%s\"));%s"), FCString::Spc(8+4), *FunctionData.CppValidationImplName, LINE_TERMINATOR);
|
|
RPCWrappers.Logf(TEXT("%sreturn;%s"), FCString::Spc(8+4), LINE_TERMINATOR); // If we got here, the validation function check failed
|
|
RPCWrappers.Logf(TEXT("%s}%s"), FCString::Spc(8), LINE_TERMINATOR);
|
|
}
|
|
|
|
// write out the return value
|
|
RPCWrappers.Log(FCString::Spc(8));
|
|
if ( Return != NULL )
|
|
{
|
|
FString ReturnType, ReturnExtendedType;
|
|
ReturnType = Return->GetCPPType(&ReturnExtendedType);
|
|
FStringOutputDevice ReplacementText(*ReturnType);
|
|
ApplyAlternatePropertyExportText(Return, ReplacementText);
|
|
ReturnType = ReplacementText;
|
|
RPCWrappers.Logf(TEXT("*(%s%s*)Result="), *ReturnType, *ReturnExtendedType);
|
|
}
|
|
|
|
// export the call to the C++ version
|
|
if (FunctionData.FunctionExportFlags & FUNCEXPORT_CppStatic)
|
|
{
|
|
RPCWrappers.Logf(TEXT("%s::%s(%s);%s"), NameLookupCPP->GetNameCPP(CurrentClass), *FunctionData.CppImplName, *ParameterList, LINE_TERMINATOR);
|
|
}
|
|
else
|
|
{
|
|
RPCWrappers.Logf(TEXT("this->%s(%s);%s"), *FunctionData.CppImplName, *ParameterList, LINE_TERMINATOR);
|
|
}
|
|
}
|
|
|
|
void FNativeClassHeaderGenerator::ExportNativeFunctions(const TArray<UFunction*>& NativeFunctions)
|
|
{
|
|
FStringOutputDevice RPCWrappers;
|
|
|
|
// find the class info for this class
|
|
FClassMetaData* ClassData = GScriptHelper->FindClassData(CurrentClass);
|
|
|
|
//@todo: UCREMOVAL this multipass stuff is only used for diffs, remove it
|
|
TArray<UFunction*> SortedNativeFunctions = NativeFunctions;
|
|
SortedNativeFunctions.Sort();
|
|
|
|
// export the C++ stubs
|
|
for (int32 Pass = 0; Pass < 2; Pass++)
|
|
{
|
|
for ( int32 i = NativeFunctions.Num() - 1; i >= 0; i-- )
|
|
{
|
|
UFunction* Function = Pass ? SortedNativeFunctions[i] : NativeFunctions[i];
|
|
FFunctionData* CompilerInfo = ClassData->FindFunctionData(Function);
|
|
check(CompilerInfo);
|
|
|
|
const FFuncInfo& FunctionData = CompilerInfo->GetFunctionData();
|
|
|
|
// Custom thunks don't get any C++ stub function generated
|
|
if (FunctionData.FunctionExportFlags & FUNCEXPORT_CustomThunk)
|
|
continue;
|
|
|
|
// All functions that are declared in class headers are by definition native
|
|
check(Function->FunctionFlags & FUNC_Native);
|
|
|
|
// Should we emit these to RPC wrappers or just ignore them?
|
|
const bool bWillBeProgrammerTyped = FunctionData.CppImplName == Function->GetName();
|
|
|
|
FStringOutputDevice WrongPass;
|
|
FStringOutputDevice& DestinationForDecl = (!bWillBeProgrammerTyped && Pass) ? RPCWrappers : WrongPass;
|
|
|
|
// Declare validation function if needed
|
|
if ( FunctionData.FunctionFlags & FUNC_NetValidate )
|
|
{
|
|
FString ParameterList;
|
|
|
|
for ( TFieldIterator<UProperty> It(Function); It && (It->PropertyFlags&(CPF_Parm|CPF_ReturnParm))==CPF_Parm; ++It )
|
|
{
|
|
UProperty* Property = *It;
|
|
|
|
if ( ParameterList.Len() )
|
|
{
|
|
ParameterList += TEXT(", ");
|
|
}
|
|
|
|
FStringOutputDevice PropertyText;
|
|
|
|
const FString* Dim = GArrayDimensions.Find(Property);
|
|
Property->ExportCppDeclaration(PropertyText, EExportedDeclaration::Parameter, Dim ? **Dim : NULL);
|
|
ApplyAlternatePropertyExportText(Property, PropertyText);
|
|
|
|
ParameterList += PropertyText;
|
|
}
|
|
DestinationForDecl.Logf( TEXT("%sbool %s(%s);\r\n"), FCString::Spc( 4 ), *FunctionData.CppValidationImplName, *ParameterList );
|
|
}
|
|
|
|
ExportNativeFunctionHeader(FunctionData, DestinationForDecl, EExportFunctionType::Function, EExportFunctionHeaderStyle::Declaration);
|
|
|
|
DestinationForDecl.Log(TEXT(";"));
|
|
if (bMultiLineUFUNCTION)
|
|
{
|
|
DestinationForDecl.Log(TEXT("\r\n"));
|
|
}
|
|
DestinationForDecl.Log(TEXT("\r\n"));
|
|
|
|
// if this function was originally declared in a base class, and it isn't a static function,
|
|
// only the C++ function header will be exported
|
|
if (!Pass || !ShouldExportFunction(Function))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// export the script wrappers
|
|
RPCWrappers.Logf(TEXT("%sDECLARE_FUNCTION(%s)"), FCString::Spc(4), *FunctionData.UnMarshallAndCallName);
|
|
RPCWrappers.Logf(TEXT("%s%s{%s"), LINE_TERMINATOR, FCString::Spc(4), LINE_TERMINATOR);
|
|
|
|
auto Parameters = GetFunctionParmsAndReturn(FunctionData.FunctionReference);
|
|
ExportFunctionThunk(RPCWrappers, FunctionData, Parameters.Parms, Parameters.Return);
|
|
|
|
RPCWrappers.Logf(TEXT("%s}%s"), FCString::Spc(4), LINE_TERMINATOR);
|
|
}
|
|
}
|
|
|
|
const TCHAR* Suffix = TEXT("_RPC_WRAPPERS");
|
|
FString MacroName = FString(NameLookupCPP->GetNameCPP(CurrentClass)) + Suffix;
|
|
FString Macroized = Macroize(*MacroName, *RPCWrappers);
|
|
GeneratedHeaderText.Log(*Macroized);
|
|
InClassMacroCalls.Logf( TEXT("%s%s\r\n"), FCString::Spc(4), *MacroName);
|
|
}
|
|
|
|
/**
|
|
* Exports the methods which trigger UnrealScript events and delegates.
|
|
*
|
|
* @param CallbackFunctions the functions to export
|
|
*/
|
|
void FNativeClassHeaderGenerator::ExportCallbackFunctions( const TArray<UFunction*>& InCallbackFunctions )
|
|
{
|
|
FStringOutputDevice RPCWrappers;
|
|
FStringOutputDevice RPCWrappersCPP;
|
|
// find the class info for this class
|
|
FClassMetaData* ClassData = GScriptHelper->FindClassData(CurrentClass);
|
|
|
|
// Skip interfaces; they don't have any callbacks
|
|
if (CurrentClass->HasAnyClassFlags(CLASS_Interface))
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<UFunction*> CallbackFunctions = InCallbackFunctions;
|
|
CallbackFunctions.Sort();
|
|
|
|
for ( int32 CallbackIndex = 0; CallbackIndex < CallbackFunctions.Num(); CallbackIndex++ )
|
|
{
|
|
UFunction* Function = CallbackFunctions[ CallbackIndex ];
|
|
|
|
// Never expecting to export delegate functions this way
|
|
check( !Function->HasAnyFunctionFlags( FUNC_Delegate ) );
|
|
|
|
UClass* Class = CurrentClass;
|
|
|
|
FFunctionData* CompilerInfo = ClassData->FindFunctionData(Function);
|
|
check(CompilerInfo);
|
|
|
|
// cache the TCHAR* for a few strings we'll use a lot here
|
|
FString FunctionName = Function->GetName();
|
|
|
|
const FFuncInfo& FunctionData = CompilerInfo->GetFunctionData();
|
|
|
|
if (FunctionData.FunctionFlags & FUNC_NetResponse)
|
|
{
|
|
// Net response functions don't go into the VM
|
|
continue;
|
|
}
|
|
|
|
FString ClassName = Class->GetName();
|
|
|
|
const bool bWillBeProgrammerTyped = Function->GetName() == FunctionData.MarshallAndCallName;
|
|
|
|
// Emit the declaration if the programmer isn't responsible for declaring this wrapper
|
|
if (!bWillBeProgrammerTyped)
|
|
{
|
|
// export the line that looks like: int32 Main(const FString& Parms)
|
|
ExportNativeFunctionHeader(FunctionData, RPCWrappers, EExportFunctionType::Event, EExportFunctionHeaderStyle::Declaration);
|
|
|
|
RPCWrappers.Log(TEXT(";\r\n"));
|
|
|
|
if (bMultiLineUFUNCTION)
|
|
{
|
|
RPCWrappers.Log(TEXT("\r\n"));
|
|
}
|
|
}
|
|
|
|
// Emit the thunk implementation
|
|
ExportNativeFunctionHeader(FunctionData, RPCWrappersCPP, EExportFunctionType::Event, EExportFunctionHeaderStyle::Definition);
|
|
|
|
auto Parameters = GetFunctionParmsAndReturn(FunctionData.FunctionReference);
|
|
|
|
WriteEventFunctionPrologue(RPCWrappersCPP, 4, Parameters, *ClassName, *FunctionName);
|
|
{
|
|
// Cast away const just in case, because ProcessEvent isn't const
|
|
RPCWrappersCPP.Logf(
|
|
TEXT("%s%sProcessEvent(FindFunctionChecked(%s_%s),%s);\r\n"),
|
|
FCString::Spc(8),
|
|
(Function->HasAllFunctionFlags(FUNC_Const)) ? *FString::Printf(TEXT("const_cast<%s*>(this)->"), NameLookupCPP->GetNameCPP(Class)) : TEXT(""),
|
|
*API,
|
|
*FunctionName,
|
|
Parameters.HasParms() ? TEXT("&Parms") : TEXT("NULL")
|
|
);
|
|
}
|
|
WriteEventFunctionEpilogue(RPCWrappersCPP, 4, Parameters, *ClassName, *FunctionName);
|
|
}
|
|
|
|
if (!CurrentClass->HasAnyClassFlags(CLASS_NoExport|CLASS_Temporary))
|
|
{
|
|
GeneratedPackageCPP.Log(*RPCWrappersCPP);
|
|
}
|
|
// else drop the implementation on the floor
|
|
|
|
const TCHAR* Suffix = TEXT("_CALLBACK_WRAPPERS");
|
|
FString MacroName = FString(NameLookupCPP->GetNameCPP(CurrentClass)) + Suffix;
|
|
FString Macroized = Macroize(*MacroName, *RPCWrappers);
|
|
GeneratedHeaderText.Log(*Macroized);
|
|
InClassMacroCalls.Logf( TEXT("%s%s\r\n"), FCString::Spc(4), *MacroName);
|
|
}
|
|
|
|
|
|
/**
|
|
* Determines if the property has alternate export text associated with it and if so replaces the text in PropertyText with the
|
|
* alternate version. (for example, structs or properties that specify a native type using export-text). Should be called immediately
|
|
* after ExportCppDeclaration()
|
|
*
|
|
* @param Prop the property that is being exported
|
|
* @param PropertyText the string containing the text exported from ExportCppDeclaration
|
|
*/
|
|
void FNativeClassHeaderGenerator::ApplyAlternatePropertyExportText( UProperty* Prop, FStringOutputDevice& PropertyText )
|
|
{
|
|
if (bIsExportingForOffsetDeterminationOnly)
|
|
{
|
|
UDelegateProperty* DelegateProperty = Cast<UDelegateProperty>(Prop);
|
|
UMulticastDelegateProperty* MulticastDelegateProperty = Cast<UMulticastDelegateProperty>(Prop);
|
|
if (DelegateProperty || MulticastDelegateProperty)
|
|
{
|
|
FString Original = Prop->GetCPPType();
|
|
FString PlaceholderOfSameSizeAndAlignemnt;
|
|
if (DelegateProperty)
|
|
{
|
|
PlaceholderOfSameSizeAndAlignemnt = TEXT("FScriptDelegate");
|
|
}
|
|
else
|
|
{
|
|
PlaceholderOfSameSizeAndAlignemnt = TEXT("FMulticastScriptDelegate");
|
|
}
|
|
PropertyText.ReplaceInline(*Original, *PlaceholderOfSameSizeAndAlignemnt, ESearchCase::CaseSensitive);
|
|
}
|
|
}
|
|
// find the class info for this class
|
|
FClassMetaData* ClassData = GScriptHelper->FindClassData(CurrentClass);
|
|
// find the compiler token for this property
|
|
FTokenData* PropData = ClassData->FindTokenData(Prop);
|
|
|
|
UObjectProperty* ObjectProp = Cast<UObjectProperty>(Prop);
|
|
if ( ObjectProp == NULL )
|
|
{
|
|
UArrayProperty* ArrayProp = Cast<UArrayProperty>(Prop);
|
|
if ( ArrayProp != NULL )
|
|
{
|
|
ObjectProp = Cast<UObjectProperty>(ArrayProp->Inner);
|
|
}
|
|
}
|
|
if ( ObjectProp != NULL && PropData && PropData->Token.ExportInfo.Len() > 0)
|
|
{
|
|
PropertyText.ReplaceInline(*(FString("class ") + NameLookupCPP->GetNameCPP(ObjectProp->PropertyClass) + TEXT("*")), *PropData->Token.ExportInfo);
|
|
return;
|
|
}
|
|
|
|
UStructProperty* StructProp = Cast<UStructProperty>(Prop);
|
|
if ( StructProp == NULL )
|
|
{
|
|
UArrayProperty* ArrayProp = Cast<UArrayProperty>(Prop);
|
|
if ( ArrayProp != NULL )
|
|
{
|
|
StructProp = Cast<UStructProperty>(ArrayProp->Inner);
|
|
}
|
|
}
|
|
|
|
if ( StructProp )
|
|
{
|
|
if ( PropData != NULL )
|
|
{
|
|
FString ExportText;
|
|
if ( PropData->Token.ExportInfo.Len() > 0 )
|
|
{
|
|
ExportText = PropData->Token.ExportInfo;
|
|
}
|
|
else
|
|
{
|
|
// we didn't have any export-text associated with the variable, so check if we have
|
|
// anything for the struct itself
|
|
FStructData* StructData = ClassData->FindStructData(StructProp->Struct);
|
|
if ( StructData != NULL && StructData->StructData.ExportInfo.Len() )
|
|
{
|
|
ExportText = StructData->StructData.ExportInfo;
|
|
}
|
|
}
|
|
|
|
if ( ExportText.Len() > 0 )
|
|
{
|
|
(FString&)PropertyText = PropertyText.Replace(NameLookupCPP->GetNameCPP(StructProp->Struct), *ExportText);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sorts the list of header files being exported from a package according to their dependency on each other.
|
|
*
|
|
* @param HeaderDependencyMap a mapping of header filenames to a list of header filenames that must be processed before that one.
|
|
* @param SortedHeaderFilenames [out] receives the sorted list of header filenames.
|
|
*/
|
|
bool FNativeClassHeaderGenerator::SortHeaderDependencyMap(const TMap<const FString*, HeaderDependents>& HeaderDependencyMap, TArray<const FString*>& SortedHeaderFilenames) const
|
|
{
|
|
SortedHeaderFilenames.Empty(HeaderDependencyMap.Num());
|
|
|
|
while (SortedHeaderFilenames.Num() < HeaderDependencyMap.Num())
|
|
{
|
|
bool bAddedSomething = false;
|
|
|
|
// Find headers with no dependencies and add those to the list.
|
|
for (auto It = HeaderDependencyMap.CreateConstIterator(); It; ++It)
|
|
{
|
|
auto Header = It->Key;
|
|
if (SortedHeaderFilenames.Contains(Header))
|
|
continue;
|
|
|
|
bool bHasRemainingDependencies = false;
|
|
for (auto It2 = It->Value.CreateConstIterator(); It2; ++It2)
|
|
{
|
|
if (!SortedHeaderFilenames.Contains(*It2))
|
|
{
|
|
bHasRemainingDependencies = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bHasRemainingDependencies)
|
|
{
|
|
// Add it to the list.
|
|
SortedHeaderFilenames.AddUnique(Header);
|
|
bAddedSomething = true;
|
|
}
|
|
}
|
|
|
|
// Circular dependency error?
|
|
if (!bAddedSomething)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FNativeClassHeaderGenerator::FindInterDependency( TMap<const FString*, HeaderDependents>& HeaderDependencyMap, const FString* Header, const FString*& OutHeader1, const FString*& OutHeader2 )
|
|
{
|
|
TSet<const FString*> VisitedHeaders;
|
|
return FindInterDependencyRecursive( HeaderDependencyMap, Header, VisitedHeaders, OutHeader1, OutHeader2 );
|
|
}
|
|
|
|
/**
|
|
* Finds to headers that are dependent on each other.
|
|
*
|
|
* @param HeaderDependencyMap A map of headers and their dependencies. Each header is represented as an index into a TArray of the actual filename strings.
|
|
* @param HeaderIndex A header to scan for any inter-dependency.
|
|
* @param VisitedHeaders Must be filled with false values before the first call (must be large enough to be indexed by all headers).
|
|
* @param OutHeader1 [out] Receives the first inter-dependent header index.
|
|
* @param OutHeader2 [out] Receives the second inter-dependent header index.
|
|
* @return true if an inter-dependency was found.
|
|
*/
|
|
bool FNativeClassHeaderGenerator::FindInterDependencyRecursive( TMap<const FString*, HeaderDependents>& HeaderDependencyMap, const FString* Header, TSet<const FString*>& VisitedHeaders, const FString*& OutHeader1, const FString*& OutHeader2 )
|
|
{
|
|
VisitedHeaders.Add(Header);
|
|
for (auto It = HeaderDependencyMap[Header].CreateConstIterator(); It; ++It)
|
|
{
|
|
auto DependentHeader = *It;
|
|
if (VisitedHeaders.Contains(DependentHeader))
|
|
{
|
|
OutHeader1 = Header;
|
|
OutHeader2 = DependentHeader;
|
|
return true;
|
|
}
|
|
|
|
if ( FindInterDependencyRecursive( HeaderDependencyMap, DependentHeader, VisitedHeaders, OutHeader1, OutHeader2 ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Finds a dependency chain between two class header files.
|
|
* Wrapper around FindDependencyChainRecursive().
|
|
*
|
|
* @param Class A class to scan for a dependency chain between the two headers.
|
|
* @param Header1 First class header filename.
|
|
* @param Header2 Second class header filename.
|
|
* @param DependencyChain [out] Receives dependency chain, if found.
|
|
* @return true if a dependency chain was found and filled in.
|
|
*/
|
|
bool FNativeClassHeaderGenerator::FindDependencyChain( FClass* Class, const FString& Header1, const FString& Header2, TArray<FClass*>& DependencyChain )
|
|
{
|
|
DependencyChain.Empty();
|
|
return FindDependencyChainRecursive( Class, Header1, Header2, false, DependencyChain );
|
|
}
|
|
|
|
/**
|
|
* Finds a dependency chain between two class header files.
|
|
*
|
|
* @param Class A class to scan for a dependency chain between the two headers.
|
|
* @param Header1 First class header filename.
|
|
* @param Header2 Second class header filename.
|
|
* @param bChainStarted Whether Header1 has been found and we've started to fill in DependencyChain. Must be false to begin with.
|
|
* @param DependencyChain [out] Receives dependency chain, if found. Must be empty before the call.
|
|
* @return true if a dependency chain was found and filled in.
|
|
*/
|
|
bool FNativeClassHeaderGenerator::FindDependencyChainRecursive( FClass* Class, const FString& Header1, const FString& Header2, bool bChainStarted, TArray<FClass*>& DependencyChain )
|
|
{
|
|
bool bIsExportClass = Class->HasAnyClassFlags(CLASS_Native) && !Class->HasAnyClassFlags(CLASS_NoExport|CLASS_Intrinsic|CLASS_Temporary);
|
|
if ( bIsExportClass )
|
|
{
|
|
auto HeaderFilename = GClassHeaderFilenameMap.FindRef(Class);
|
|
|
|
bChainStarted = bChainStarted || HeaderFilename == Header1;
|
|
|
|
if ( bChainStarted )
|
|
{
|
|
DependencyChain.Add( Class );
|
|
if ( HeaderFilename == Header2 )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
FClass* SuperClass = Class->GetSuperClass();
|
|
if ( SuperClass && SuperClass->GetOuter() == Class->GetOuter() && FindDependencyChainRecursive( SuperClass, Header1, Header2, bChainStarted, DependencyChain ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
for (FClass* InterfaceClass : Class->GetInterfaceTypes())
|
|
{
|
|
if ( InterfaceClass->GetOuter() == Class->GetOuter() && FindDependencyChainRecursive( InterfaceClass, Header1, Header2, bChainStarted, DependencyChain ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if ( bIsExportClass && bChainStarted )
|
|
{
|
|
DependencyChain.RemoveAtSwap( DependencyChain.Num() - 1 );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Constructor.
|
|
FNativeClassHeaderGenerator::FNativeClassHeaderGenerator( UPackage* InPackage, FClasses& AllClasses, bool InAllowSaveExportedHeaders, bool bInUseRelativePaths )
|
|
: CurrentClass (NULL)
|
|
, API (FPackageName::GetShortName(InPackage).ToUpper())
|
|
, Package (InPackage)
|
|
, bIsExportingForOffsetDeterminationOnly(false)
|
|
, bAllowSaveExportedHeaders (InAllowSaveExportedHeaders)
|
|
, bFailIfGeneratedCodeChanges (false)
|
|
, bUseRelativePaths (bInUseRelativePaths)
|
|
{
|
|
const FString PackageName = FPackageName::GetShortName(Package);
|
|
|
|
GeneratedCPPFilenameBase = PackageName + TEXT(".generated");
|
|
GeneratedProtoFilenameBase = PackageName + TEXT(".generated");
|
|
GeneratedMCPFilenameBase = PackageName + TEXT(".generated");
|
|
bFailIfGeneratedCodeChanges = FParse::Param(FCommandLine::Get(), TEXT("FailIfGeneratedCodeChanges"));
|
|
|
|
// Tag native classes in this package for export.
|
|
|
|
Classes = AllClasses.GetClassesInPackage();
|
|
|
|
TMap<FName,UClass*> ClassNameMap;
|
|
for ( int32 ClassIndex = 0; ClassIndex < Classes.Num(); ClassIndex++ )
|
|
{
|
|
ClassNameMap.Add(Classes[ClassIndex]->GetFName(), Classes[ClassIndex]);
|
|
}
|
|
|
|
class UnsortedHeaderFilenamesType
|
|
{
|
|
public:
|
|
FString* AddUnique(const FString& Str)
|
|
{
|
|
for (auto It = Data.CreateConstIterator(); It; ++It)
|
|
{
|
|
auto& Ptr = *It;
|
|
|
|
if (*Ptr == Str)
|
|
return &*Ptr;
|
|
}
|
|
|
|
return Data[Data.Add(MakeUnique<FString>(Str))].Get();
|
|
}
|
|
|
|
TArray<TUniquePtr<FString>> Data;
|
|
};
|
|
|
|
UnsortedHeaderFilenamesType UnsortedHeaderFilenames;
|
|
TArray<const FString*> HeaderFilenames; // These reference the strings stored in UnsortedHeaderFilenames
|
|
|
|
bool bCircularDependencyDetected = false;
|
|
{
|
|
TMap<const FString*, HeaderDependents> HeaderDependencyMap;
|
|
TArray<FClass*> ClassesInPackage = AllClasses.GetClassesInPackage(Package);
|
|
for (FClass* Cls : ClassesInPackage)
|
|
{
|
|
auto ClsHeaderFilename = GClassHeaderFilenameMap.FindRef(Cls);
|
|
|
|
bool bIsExportClass = Cls->HasAnyClassFlags(CLASS_Native) && !Cls->HasAnyClassFlags(CLASS_NoExport|CLASS_Intrinsic|CLASS_Temporary);
|
|
if ( bIsExportClass )
|
|
{
|
|
auto* HeaderFilename = UnsortedHeaderFilenames.AddUnique(ClsHeaderFilename);
|
|
auto& Dependencies = HeaderDependencyMap.FindOrAdd(HeaderFilename);
|
|
|
|
// Add the super class' header as a dependency if it's different.
|
|
UClass* SuperClass = Cls->GetSuperClass();
|
|
bool bIsDependentExportClass = SuperClass && SuperClass->HasAnyClassFlags(CLASS_Native) && !SuperClass->HasAnyClassFlags(CLASS_NoExport|CLASS_Intrinsic|CLASS_Temporary);
|
|
if ( bIsDependentExportClass && SuperClass->GetOuter() == Cls->GetOuter() )
|
|
{
|
|
auto SuperClassHeaderFilename = GClassHeaderFilenameMap.FindRef(SuperClass);
|
|
if ( SuperClassHeaderFilename != ClsHeaderFilename )
|
|
{
|
|
auto* DependentHeaderFilename = UnsortedHeaderFilenames.AddUnique(SuperClassHeaderFilename);
|
|
Dependencies.AddUnique(DependentHeaderFilename);
|
|
}
|
|
}
|
|
|
|
// Add base interface headers as dependencies, if they're different.
|
|
for (TArray<FImplementedInterface>::TIterator It(Cls->Interfaces); It; ++It)
|
|
{
|
|
UClass* InterfaceClass = It->Class;
|
|
bool bIsDependentExportClass = InterfaceClass->HasAnyClassFlags(CLASS_Native) && !InterfaceClass->HasAnyClassFlags(CLASS_NoExport|CLASS_Intrinsic|CLASS_Temporary);
|
|
if ( bIsDependentExportClass && InterfaceClass->GetOuter() == Cls->GetOuter() )
|
|
{
|
|
auto InterfaceClassHeaderFilename = GClassHeaderFilenameMap.FindRef(InterfaceClass);
|
|
if ( InterfaceClassHeaderFilename != ClsHeaderFilename )
|
|
{
|
|
auto* DependentHeaderFilename = UnsortedHeaderFilenames.AddUnique(InterfaceClassHeaderFilename);
|
|
Dependencies.AddUnique(DependentHeaderFilename);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (Cls->HasAnyClassFlags(CLASS_Temporary))
|
|
{
|
|
// Struct only headers can generate header groups too so make sure their added too.
|
|
auto* HeaderFilename = UnsortedHeaderFilenames.AddUnique(ClsHeaderFilename);
|
|
HeaderDependencyMap.FindOrAdd(HeaderFilename);
|
|
}
|
|
}
|
|
bCircularDependencyDetected = !SortHeaderDependencyMap(HeaderDependencyMap, HeaderFilenames);
|
|
if (bCircularDependencyDetected)
|
|
{
|
|
// Find one circular path (though there may be multiple).
|
|
for (auto HeaderIt = HeaderDependencyMap.CreateConstIterator(); HeaderIt; ++HeaderIt)
|
|
{
|
|
const FString* Header = HeaderIt->Key;
|
|
|
|
const FString* ClassHeaderFilename1;
|
|
const FString* ClassHeaderFilename2;
|
|
if (!FindInterDependency(HeaderDependencyMap, Header, ClassHeaderFilename1, ClassHeaderFilename2))
|
|
continue;
|
|
|
|
TArray<FClass*> DependencyChain1;
|
|
for (FClass* Class : ClassesInPackage)
|
|
{
|
|
FindDependencyChain( Class, *ClassHeaderFilename1, *ClassHeaderFilename2, DependencyChain1 );
|
|
|
|
if (DependencyChain1.Num())
|
|
break;
|
|
}
|
|
|
|
TArray<FClass*> DependencyChain2;
|
|
for (FClass* Class : ClassesInPackage)
|
|
{
|
|
FindDependencyChain( Class, *ClassHeaderFilename2, *ClassHeaderFilename1, DependencyChain2 );
|
|
|
|
if (DependencyChain2.Num())
|
|
break;
|
|
}
|
|
|
|
if (!DependencyChain1.Num() || !DependencyChain2.Num())
|
|
continue;
|
|
|
|
FString DependencyChainString1;
|
|
for (FClass* Dependency : DependencyChain1)
|
|
{
|
|
if (!DependencyChainString1.IsEmpty())
|
|
{
|
|
DependencyChainString1 += TEXT(" -> ");
|
|
}
|
|
|
|
DependencyChainString1 += Dependency->GetName();
|
|
}
|
|
|
|
FString DependencyChainString2;
|
|
for (FClass* Dependency : DependencyChain2)
|
|
{
|
|
if (!DependencyChainString1.IsEmpty())
|
|
{
|
|
DependencyChainString2 += TEXT(" -> ");
|
|
}
|
|
|
|
DependencyChainString2 += Dependency->GetName();
|
|
}
|
|
|
|
UE_LOG(LogCompile, Error, TEXT("Header interdependency: %s <-> %s (%s and %s)."),
|
|
*(PackageName + *ClassHeaderFilename1 + TEXT("Classes.h")),
|
|
*(PackageName + *ClassHeaderFilename2 + TEXT("Classes.h")),
|
|
*DependencyChainString1,
|
|
*DependencyChainString2 );
|
|
break;
|
|
}
|
|
UE_LOG(LogCompile, Error, TEXT("Interdependent headers detected - aborting!") );
|
|
}
|
|
}
|
|
|
|
bool bHasNamesForExport = false;
|
|
TempHeaderPaths.Empty();
|
|
PackageHeaderPaths.Empty();
|
|
// Reset header generation output strings
|
|
GeneratedPackageCPP.Empty();
|
|
GeneratedProtoText.Empty();
|
|
GeneratedMCPText.Empty();
|
|
CrossModuleGeneratedFunctionDeclarations.Empty();
|
|
UniqueCrossModuleReferences.Empty();
|
|
GeneratedFunctionDeclarations.Empty();
|
|
GeneratedFunctionBodyTextSplit.Empty();
|
|
|
|
for (auto It = HeaderFilenames.CreateConstIterator(); !bCircularDependencyDetected && It; ++It)
|
|
{
|
|
ClassHeaderFilename = **It;
|
|
|
|
FString PkgDir;
|
|
FString GeneratedIncludeDirectory;
|
|
if (MakeCommandlet_FindPackageLocation(*PackageName, PkgDir, GeneratedIncludeDirectory) == false)
|
|
{
|
|
UE_LOG(LogCompile, Error, TEXT("Failed to find path for package %s"), *PackageName);
|
|
}
|
|
ClassesHeaderPath = GeneratedIncludeDirectory / PackageName + ClassHeaderFilename + TEXT("Classes.h");
|
|
|
|
int32 ClassCount = 0;
|
|
for( int32 ClassIndex = 0; ClassIndex < Classes.Num(); ClassIndex++ )
|
|
{
|
|
UClass* Class = Classes[ClassIndex];
|
|
|
|
if( Class->GetOuter()==Package && Class->HasAnyClassFlags(CLASS_Native|CLASS_Temporary) && GClassHeaderFilenameMap.FindRef(Class) == ClassHeaderFilename )
|
|
{
|
|
if (GClassStrippedHeaderTextMap.Contains(Class) && !Class->HasAnyClassFlags(CLASS_NoExport))
|
|
{
|
|
ClassCount++;
|
|
// Skip temporary classes autogenerated in struct-only headers.
|
|
if (!Class->HasAnyClassFlags(CLASS_Temporary))
|
|
{
|
|
Class->UnMark(OBJECTMARK_TagImp);
|
|
Class->Mark(OBJECTMARK_TagExp);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Class->UnMark(EObjectMark(OBJECTMARK_TagImp | OBJECTMARK_TagExp));
|
|
}
|
|
}
|
|
|
|
if( ClassCount == 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CurrentClass = NULL;
|
|
ListOfPublicClassesUObjectHeaderIncludes.Empty();
|
|
ListOfAllUObjectHeaderIncludes.Empty();
|
|
OriginalHeader.Empty();
|
|
PreHeaderText.Empty();
|
|
|
|
// Load the original header file into memory
|
|
FFileHelper::LoadFileToString(OriginalHeader,*ClassesHeaderPath);
|
|
|
|
UE_LOG(LogCompile, Log, TEXT("Autogenerating C++ header: %s"), *ClassHeaderFilename );
|
|
|
|
// Write the classes and enums header prefixes.
|
|
|
|
PreHeaderText.Logf(
|
|
TEXT("// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.\r\n")
|
|
TEXT("/*===========================================================================\r\n")
|
|
TEXT(" C++ class boilerplate exported from UnrealHeaderTool.\r\n")
|
|
TEXT(" This is automatically generated by the tools.\r\n")
|
|
TEXT(" DO NOT modify this manually! Edit the corresponding .h files instead!\r\n")
|
|
TEXT("===========================================================================*/\r\n")
|
|
TEXT("#pragma once\r\n")
|
|
TEXT("\r\n")
|
|
);
|
|
|
|
// if a global auto-include file exists, generate a line to have that file included
|
|
FString GlobalAutoIncludeFilename = PackageName + ClassHeaderFilename + TEXT("GlobalIncludes.h");
|
|
const FString StandardHeaderFileLocation = PkgDir / TEXT("Public");
|
|
if ( IFileManager::Get().FileSize(*(StandardHeaderFileLocation / GlobalAutoIncludeFilename)) > 0 )
|
|
{
|
|
PreHeaderText.Logf(TEXT("#include \"%s\"\r\n\r\n"), *GlobalAutoIncludeFilename);
|
|
}
|
|
|
|
FString HeaderAPI = API;
|
|
if( ClassHeaderFilename.Len() > 0 )
|
|
{
|
|
HeaderAPI = HeaderAPI + TEXT("_") + ClassHeaderFilename.ToUpper();
|
|
}
|
|
|
|
// Export an include line for each header
|
|
for ( int32 ClassIndex = 0; ClassIndex < Classes.Num(); ClassIndex++ )
|
|
{
|
|
UClass* Class = Classes[ClassIndex];
|
|
if ( GClassHeaderFilenameMap.FindRef(Class) == ClassHeaderFilename && Class->GetOuter() == Package )
|
|
{
|
|
ExportClassHeader(Class);
|
|
}
|
|
}
|
|
|
|
ListOfPublicClassesUObjectHeaderIncludes.Logf( TEXT("\r\n") );
|
|
|
|
// build the full header file out of its pieces
|
|
const FString FullClassesHeader = FString::Printf(
|
|
TEXT("%s\r\n%s"),
|
|
*PreHeaderText,
|
|
*ListOfPublicClassesUObjectHeaderIncludes
|
|
);
|
|
|
|
// Save the classes header if it has changed.
|
|
SaveHeaderIfChanged(*ClassesHeaderPath,*FullClassesHeader);
|
|
}
|
|
|
|
if (HeaderFilenames.Num() == 0)
|
|
{
|
|
// If no headers are generated, check if there's any no export classes that need to have the inl file generated.
|
|
for ( int32 ClassIndex = 0; ClassIndex < Classes.Num(); ClassIndex++ )
|
|
{
|
|
UClass* Class = Classes[ClassIndex];
|
|
if ( Class->GetOuter() == Package && !Class->HasAnyClassFlags(CLASS_Intrinsic|CLASS_Temporary) )
|
|
{
|
|
ExportClassHeader(Class);
|
|
}
|
|
}
|
|
}
|
|
|
|
// now export the names for the functions in this package
|
|
// notice we always export this file (as opposed to only exporting if we have any marked names)
|
|
// because there would be no way to know when the file was created otherwise
|
|
if (!bCircularDependencyDetected)
|
|
{
|
|
// Export ..generated.cpp
|
|
ExportGeneratedCPP();
|
|
}
|
|
|
|
ExportGeneratedProto();
|
|
|
|
ExportGeneratedMCP();
|
|
|
|
// Export all changed headers from their temp files to the .h files
|
|
ExportUpdatedHeaders(PackageName);
|
|
|
|
// Delete stale *.generated.h files
|
|
DeleteUnusedGeneratedHeaders();
|
|
}
|
|
|
|
void FNativeClassHeaderGenerator::DeleteUnusedGeneratedHeaders()
|
|
{
|
|
TArray<FString> AllIntermediateFolders;
|
|
|
|
for (auto It = PackageHeaderPaths.CreateConstIterator(); It; ++It)
|
|
{
|
|
const FString IntermediatePath = FPaths::GetPath(*It);
|
|
|
|
if (AllIntermediateFolders.Contains(IntermediatePath))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
AllIntermediateFolders.Add( IntermediatePath );
|
|
|
|
TArray<FString> AllHeaders;
|
|
IFileManager::Get().FindFiles( AllHeaders, *(IntermediatePath / TEXT("*.generated.h")), true, false );
|
|
|
|
for (auto It = AllHeaders.CreateConstIterator(); It; ++It)
|
|
{
|
|
const FString HeaderPath = IntermediatePath / *It;
|
|
|
|
if (PackageHeaderPaths.Contains(HeaderPath))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Check intrinsic classes. Get the class name from file name by removing .generated.h.
|
|
const FString HeaderFilename = FPaths::GetBaseFilename(HeaderPath);
|
|
const int32 GeneratedIndex = HeaderFilename.Find(TEXT(".generated"), ESearchCase::IgnoreCase, ESearchDir::FromEnd);
|
|
const FString ClassName = HeaderFilename.Mid(0, GeneratedIndex);
|
|
UClass* IntrinsicClass = FindObject<UClass>(ANY_PACKAGE, *ClassName);
|
|
if (!IntrinsicClass || !IntrinsicClass->HasAnyClassFlags(CLASS_Intrinsic))
|
|
{
|
|
IFileManager::Get().Delete(*HeaderPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dirty hack global variable to allow different result codes passed through
|
|
* exceptions. Needs to be fixed in future versions of UHT.
|
|
*/
|
|
ECompilationResult::Type GCompilationResult = ECompilationResult::OtherCompilationError;
|
|
|
|
bool FNativeClassHeaderGenerator::SaveHeaderIfChanged(const TCHAR* HeaderPath, const TCHAR* InNewHeaderContents)
|
|
{
|
|
if ( !bAllowSaveExportedHeaders )
|
|
{
|
|
// Return false indicating that the header did not need updating
|
|
return false;
|
|
}
|
|
|
|
FString Tabified = Tabify(InNewHeaderContents);
|
|
const TCHAR* NewHeaderContents = *Tabified;
|
|
static bool bTestedCmdLine = false;
|
|
if (!bTestedCmdLine)
|
|
{
|
|
bTestedCmdLine = true;
|
|
if( FParse::Param( FCommandLine::Get(), TEXT("WRITEREF") ) )
|
|
{
|
|
bWriteContents = true;
|
|
UE_LOG(LogCompile, Log, TEXT("********************************* Writing reference generated code to ReferenceGeneratedCode."));
|
|
UE_LOG(LogCompile, Log, TEXT("********************************* Deleting all files in ReferenceGeneratedCode."));
|
|
IFileManager::Get().DeleteDirectory(*(FString(FPaths::GameSavedDir()) / TEXT("ReferenceGeneratedCode/")), false, true);
|
|
IFileManager::Get().MakeDirectory(*(FString(FPaths::GameSavedDir()) / TEXT("ReferenceGeneratedCode/")));
|
|
}
|
|
else if( FParse::Param( FCommandLine::Get(), TEXT("VERIFYREF") ) )
|
|
{
|
|
bVerifyContents = true;
|
|
UE_LOG(LogCompile, Log, TEXT("********************************* Writing generated code to VerifyGeneratedCode and comparing to ReferenceGeneratedCode"));
|
|
UE_LOG(LogCompile, Log, TEXT("********************************* Deleting all files in VerifyGeneratedCode."));
|
|
IFileManager::Get().DeleteDirectory(*(FString(FPaths::GameSavedDir()) / TEXT("VerifyGeneratedCode/")), false, true);
|
|
IFileManager::Get().MakeDirectory(*(FString(FPaths::GameSavedDir()) / TEXT("VerifyGeneratedCode/")));
|
|
}
|
|
}
|
|
|
|
if (bWriteContents || bVerifyContents)
|
|
{
|
|
FString Ref = FString(FPaths::GameSavedDir()) / TEXT("ReferenceGeneratedCode") / FPaths::GetCleanFilename(HeaderPath);
|
|
FString Verify = FString(FPaths::GameSavedDir()) / TEXT("VerifyGeneratedCode") / FPaths::GetCleanFilename(HeaderPath);
|
|
|
|
if (bWriteContents)
|
|
{
|
|
int32 i;
|
|
for (i = 0 ;i < 10; i++)
|
|
{
|
|
if (FFileHelper::SaveStringToFile(NewHeaderContents, *Ref))
|
|
{
|
|
break;
|
|
}
|
|
FPlatformProcess::Sleep(1.0f); // I don't know why this fails after we delete the directory
|
|
}
|
|
check(i<10);
|
|
}
|
|
else
|
|
{
|
|
int32 i;
|
|
for (i = 0 ;i < 10; i++)
|
|
{
|
|
if (FFileHelper::SaveStringToFile(NewHeaderContents, *Verify))
|
|
{
|
|
break;
|
|
}
|
|
FPlatformProcess::Sleep(1.0f); // I don't know why this fails after we delete the directory
|
|
}
|
|
check(i<10);
|
|
FString RefHeader;
|
|
FString Message;
|
|
if (!FFileHelper::LoadFileToString(RefHeader, *Ref))
|
|
{
|
|
Message = FString::Printf(TEXT("********************************* %s appears to be a new generated file."), *FPaths::GetCleanFilename(HeaderPath));
|
|
}
|
|
else
|
|
{
|
|
if (FCString::Strcmp(NewHeaderContents, *RefHeader) != 0)
|
|
{
|
|
Message = FString::Printf(TEXT("********************************* %s has changed."), *FPaths::GetCleanFilename(HeaderPath));
|
|
}
|
|
}
|
|
if (Message.Len())
|
|
{
|
|
UE_LOG(LogCompile, Log, TEXT("%s"), *Message);
|
|
ChangeMessages.AddUnique(Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
FString OriginalHeaderLocal;
|
|
FFileHelper::LoadFileToString(OriginalHeaderLocal, HeaderPath);
|
|
|
|
const bool bHasChanged = OriginalHeaderLocal.Len() == 0 || FCString::Strcmp(*OriginalHeaderLocal, NewHeaderContents);
|
|
if (bHasChanged)
|
|
{
|
|
if (bFailIfGeneratedCodeChanges)
|
|
{
|
|
FString ConflictPath = FString(HeaderPath) + TEXT(".conflict");
|
|
FFileHelper::SaveStringToFile(NewHeaderContents, *ConflictPath);
|
|
|
|
GCompilationResult = ECompilationResult::FailedDueToHeaderChange;
|
|
FError::Throwf(TEXT("ERROR: '%s': Changes to generated code are not allowed - conflicts written to '%s'"), HeaderPath, *ConflictPath);
|
|
}
|
|
|
|
// save the updated version to a tmp file so that the user can see what will be changing
|
|
const FString TmpHeaderFilename = GenerateTempHeaderName( HeaderPath, false );
|
|
|
|
// delete any existing temp file
|
|
IFileManager::Get().Delete( *TmpHeaderFilename, false, true );
|
|
if ( !FFileHelper::SaveStringToFile(NewHeaderContents, *TmpHeaderFilename) )
|
|
{
|
|
UE_LOG(LogCompile, Warning, TEXT("Failed to save header export preview: '%s'"), *TmpHeaderFilename);
|
|
}
|
|
|
|
TempHeaderPaths.Add(TmpHeaderFilename);
|
|
}
|
|
|
|
// Remember this header filename to be able to check for any old (unused) headers later.
|
|
PackageHeaderPaths.Add( FString(HeaderPath).Replace( TEXT( "\\" ), TEXT( "/" ) ) );
|
|
|
|
return bHasChanged;
|
|
}
|
|
|
|
/**
|
|
* Create a temp header file name from the header name
|
|
*
|
|
* @param CurrentFilename The filename off of which the current filename will be generated
|
|
* @param bReverseOperation Get the header from the temp file name instead
|
|
*
|
|
* @return The generated string
|
|
*/
|
|
FString FNativeClassHeaderGenerator::GenerateTempHeaderName( FString CurrentFilename, bool bReverseOperation )
|
|
{
|
|
return bReverseOperation
|
|
? CurrentFilename.Replace(TEXT(".tmp"), TEXT(""))
|
|
: CurrentFilename + TEXT(".tmp");
|
|
}
|
|
|
|
/**
|
|
* Exports the temp header files into the .h files, then deletes the temp files.
|
|
*
|
|
* @param PackageName Name of the package being saved
|
|
*/
|
|
void FNativeClassHeaderGenerator::ExportUpdatedHeaders(FString PackageName)
|
|
{
|
|
for (auto It = TempHeaderPaths.CreateConstIterator(); It; ++It)
|
|
{
|
|
const FString& TmpFilename = *It;
|
|
|
|
FString Filename = GenerateTempHeaderName( TmpFilename, true );
|
|
if (!IFileManager::Get().Move(*Filename, *TmpFilename, true, true))
|
|
{
|
|
UE_LOG(LogCompile, Error, TEXT("%s"), *FString::Printf(TEXT("Error exporting %s: couldn't write file '%s'"),*PackageName,*Filename));
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogCompile, Log, TEXT("Exported updated C++ header: %s"), *Filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Exports protobuffer definitions from boilerplate that was generated for a package.
|
|
* They are exported to a file using the name <PackageName>.generated.proto
|
|
*/
|
|
void FNativeClassHeaderGenerator::ExportGeneratedProto()
|
|
{
|
|
if (GeneratedProtoText.Len())
|
|
{
|
|
UE_LOG(LogCompile, Log, TEXT("Autogenerating boilerplate proto: %s.proto"), *GeneratedProtoFilenameBase );
|
|
|
|
FStringOutputDevice ProtoPreamble;
|
|
|
|
ProtoPreamble.Logf(
|
|
TEXT("// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.") LINE_TERMINATOR
|
|
TEXT("/*===========================================================================") LINE_TERMINATOR
|
|
TEXT(" Purpose: The file defines our Google Protocol Buffers which are used in over ") LINE_TERMINATOR
|
|
TEXT(" the wire messages between servers as well as between clients and servers.") LINE_TERMINATOR
|
|
TEXT(" This is automatically generated by UnrealHeaderTool.") LINE_TERMINATOR
|
|
TEXT(" DO NOT modify this manually! Edit the corresponding .h files instead!") LINE_TERMINATOR
|
|
TEXT("===========================================================================*/") LINE_TERMINATOR
|
|
LINE_TERMINATOR
|
|
TEXT("// We care more about speed than code size") LINE_TERMINATOR
|
|
TEXT("option optimize_for = SPEED;") LINE_TERMINATOR
|
|
TEXT("// We don't use the service generation functionality") LINE_TERMINATOR
|
|
TEXT("option cc_generic_services = false;") LINE_TERMINATOR
|
|
LINE_TERMINATOR
|
|
);
|
|
|
|
FString PkgName = FPackageName::GetShortName(Package);
|
|
FString PkgDir;
|
|
FString GeneratedIncludeDirectory;
|
|
if (MakeCommandlet_FindPackageLocation(*PkgName, PkgDir, GeneratedIncludeDirectory) == false)
|
|
{
|
|
UE_LOG(LogCompile, Error, TEXT("Failed to find path for package %s"), *PkgName);
|
|
}
|
|
|
|
FString HeaderPath = GeneratedIncludeDirectory / GeneratedProtoFilenameBase + TEXT(".proto");
|
|
SaveHeaderIfChanged(*HeaderPath, *(ProtoPreamble + GeneratedProtoText));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Exports MCPbuffer definitions from boilerplate that was generated for a package.
|
|
* They are exported to a file using the name <PackageName>.generated.MCP
|
|
*/
|
|
void FNativeClassHeaderGenerator::ExportGeneratedMCP()
|
|
{
|
|
if (GeneratedMCPText.Len())
|
|
{
|
|
UE_LOG(LogCompile, Log, TEXT("Autogenerating boilerplate MCP: %s.java"), *GeneratedMCPFilenameBase );
|
|
|
|
FStringOutputDevice MCPPreamble;
|
|
|
|
MCPPreamble.Logf(
|
|
TEXT("// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.") LINE_TERMINATOR
|
|
TEXT("/*===========================================================================") LINE_TERMINATOR
|
|
TEXT(" Purpose: The file defines java heaers for MCP rpc messages. ") LINE_TERMINATOR
|
|
TEXT(" DO NOT modify this manually! Edit the corresponding .h files instead!") LINE_TERMINATOR
|
|
TEXT("===========================================================================*/") LINE_TERMINATOR
|
|
);
|
|
|
|
FString PkgName = FPackageName::GetShortName(Package);
|
|
FString PkgDir;
|
|
FString GeneratedIncludeDirectory;
|
|
if (MakeCommandlet_FindPackageLocation(*PkgName, PkgDir, GeneratedIncludeDirectory) == false)
|
|
{
|
|
UE_LOG(LogCompile, Error, TEXT("Failed to find path for package %s"), *PkgName);
|
|
}
|
|
|
|
FString HeaderPath = GeneratedIncludeDirectory / GeneratedMCPFilenameBase + TEXT(".java");
|
|
SaveHeaderIfChanged(*HeaderPath, *(MCPPreamble + GeneratedMCPText));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Exports C++ definitions for boilerplate that was generated for a package.
|
|
* They are exported to a file using the name <PackageName>.generated.inl
|
|
* @param ReferencedNames list of function names to export.
|
|
*/
|
|
void FNativeClassHeaderGenerator::ExportGeneratedCPP()
|
|
{
|
|
FStringOutputDevice GeneratedCPPPreamble;
|
|
FStringOutputDevice GeneratedCPPEpilogue;
|
|
FStringOutputDevice GeneratedCPPText;
|
|
|
|
TArray<FStringOutputDevice> GeneratedCPPFiles;
|
|
|
|
UE_LOG(LogCompile, Log, TEXT("Autogenerating boilerplate cpp: %s.cpp"), *GeneratedCPPFilenameBase );
|
|
|
|
GeneratedCPPPreamble.Logf(
|
|
TEXT("// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.") LINE_TERMINATOR
|
|
TEXT("/*===========================================================================") LINE_TERMINATOR
|
|
TEXT(" Boilerplate C++ definitions for a single module.") LINE_TERMINATOR
|
|
TEXT(" This is automatically generated by UnrealHeaderTool.") LINE_TERMINATOR
|
|
TEXT(" DO NOT modify this manually! Edit the corresponding .h files instead!") LINE_TERMINATOR
|
|
TEXT("===========================================================================*/") LINE_TERMINATOR
|
|
LINE_TERMINATOR
|
|
);
|
|
|
|
// Include all the UObject headers for this module first, in the same order that we digested them
|
|
GeneratedCPPText.Logf( *ListOfAllUObjectHeaderIncludes );
|
|
|
|
GeneratedCPPText.Log(*GeneratedPackageCPP);
|
|
// Add all names marked for export to a list (for sorting)
|
|
TArray<FString> Names;
|
|
for (TSet<FName>::TIterator It(ReferencedNames); It; ++It)
|
|
{
|
|
Names.Add(It->ToString());
|
|
}
|
|
|
|
// Autogenerate names (alphabetically sorted).
|
|
Names.Sort();
|
|
for( int32 NameIndex=0; NameIndex<Names.Num(); NameIndex++ )
|
|
{
|
|
GeneratedCPPText.Logf( TEXT("FName %s_%s = FName(TEXT(\"%s\"));") LINE_TERMINATOR, *API, *Names[NameIndex], *Names[NameIndex] );
|
|
}
|
|
|
|
GeneratedCPPEpilogue.Logf(
|
|
LINE_TERMINATOR
|
|
);
|
|
|
|
FString PkgName = FPackageName::GetShortName(Package);
|
|
FString PkgDir;
|
|
FString GeneratedIncludeDirectory;
|
|
if (MakeCommandlet_FindPackageLocation(*PkgName, PkgDir, GeneratedIncludeDirectory) == false)
|
|
{
|
|
UE_LOG(LogCompile, Error, TEXT("Failed to find path for package %s"), *PkgName);
|
|
}
|
|
if (GeneratedFunctionDeclarations.Len() || CrossModuleGeneratedFunctionDeclarations.Len())
|
|
{
|
|
ExportGeneratedPackageInitCode(Package);
|
|
}
|
|
|
|
TArray<FString> NumberedHeaderNames;
|
|
|
|
// Generate each of the .generated.inl files
|
|
for( int32 FileIdx=0;FileIdx<GeneratedFunctionBodyTextSplit.Num();FileIdx++ )
|
|
{
|
|
FStringOutputDevice FileText;
|
|
// The first file has all of the GeneratedCPPText, only the functions are split.
|
|
if( FileIdx == 0)
|
|
{
|
|
FileText = GeneratedCPPText;
|
|
}
|
|
|
|
if (GeneratedFunctionDeclarations.Len() || CrossModuleGeneratedFunctionDeclarations.Len())
|
|
{
|
|
FileText.Logf(TEXT("#if USE_COMPILED_IN_NATIVES\r\n"));
|
|
if (CrossModuleGeneratedFunctionDeclarations.Len())
|
|
{
|
|
FileText.Logf(TEXT("// Cross Module References\r\n"));
|
|
FileText.Log(CrossModuleGeneratedFunctionDeclarations);
|
|
FileText.Logf(TEXT("\r\n"));
|
|
}
|
|
FileText.Log(GeneratedFunctionDeclarations);
|
|
FileText.Log(GeneratedFunctionBodyTextSplit[FileIdx]);
|
|
FileText.Logf(TEXT("#endif\r\n"));
|
|
}
|
|
|
|
FString HeaderName = GeneratedCPPFilenameBase + (GeneratedFunctionBodyTextSplit.Num() > 1 ? *FString::Printf(TEXT(".%d.inl"),FileIdx+1) : TEXT(".inl"));
|
|
FString HeaderPath = GeneratedIncludeDirectory / HeaderName;
|
|
|
|
SaveHeaderIfChanged(*HeaderPath, *(GeneratedCPPPreamble + FileText + GeneratedCPPEpilogue));
|
|
|
|
if (GeneratedFunctionBodyTextSplit.Num() > 1)
|
|
{
|
|
NumberedHeaderNames.Add(HeaderName);
|
|
}
|
|
}
|
|
|
|
if (GeneratedFunctionBodyTextSplit.Num() > 1)
|
|
{
|
|
FStringOutputDevice FileText;
|
|
|
|
for (int32 i=0; i < NumberedHeaderNames.Num(); ++i)
|
|
{
|
|
FileText.Logf(TEXT("#include \"%s\"") LINE_TERMINATOR, *NumberedHeaderNames[i]);
|
|
}
|
|
|
|
FString HeaderPath = GeneratedIncludeDirectory / GeneratedCPPFilenameBase + TEXT(".inl");
|
|
SaveHeaderIfChanged(*HeaderPath, *(GeneratedCPPPreamble + FileText + GeneratedCPPEpilogue));
|
|
}
|
|
|
|
if( FParse::Param( FCommandLine::Get(), TEXT("GenerateConstructors") ) && AllConstructors.Len())
|
|
{
|
|
const FString PrivateLoc = PkgDir / PkgName / TEXT("Private");
|
|
FString ConstructorPath = PrivateLoc / PkgName + TEXT("Constructors.cpp");
|
|
FString ConstructorsWithIfdef(TEXT("// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.\r\n"));
|
|
ConstructorsWithIfdef += *Tabify(*AllConstructors);
|
|
SaveHeaderIfChanged(*ConstructorPath,*ConstructorsWithIfdef);
|
|
}
|
|
}
|
|
|
|
|
|
ECompilationResult::Type UnrealHeaderTool_Main(const FString& ModuleInfoFilename)
|
|
{
|
|
check(GIsUCCMakeStandaloneHeaderGenerator);
|
|
ECompilationResult::Type Result = ECompilationResult::Succeeded;
|
|
|
|
if ( !FParse::Param( FCommandLine::Get(), TEXT("IgnoreWarnings")) )
|
|
{
|
|
GWarn->TreatWarningsAsErrors = true;
|
|
}
|
|
|
|
FString ModuleInfoPath = FPaths::GetPath(ModuleInfoFilename);
|
|
|
|
// Load the manifest file, giving a list of all modules to be processed, pre-sorted by dependency ordering
|
|
try
|
|
{
|
|
GManifest = FManifest::LoadFromFile(ModuleInfoFilename);
|
|
}
|
|
catch (const TCHAR* Ex)
|
|
{
|
|
UE_LOG(LogCompile, Error, TEXT("Failed to load manifest file '%s': %s"), *ModuleInfoFilename, Ex);
|
|
return GCompilationResult;
|
|
}
|
|
|
|
NameLookupCPP = new FNameLookupCPP();
|
|
|
|
FCompilerMetadataManager* ScriptHelper = new FCompilerMetadataManager();
|
|
GScriptHelper = ScriptHelper;
|
|
|
|
// Load classes for editing.
|
|
int32 NumFailures = 0;
|
|
|
|
// Three passes. 1) Public 'Classes' headers (legacy) 2) Public headers 3) Private headers
|
|
for( int32 PassIndex = 0; PassIndex < 3; ++PassIndex )
|
|
{
|
|
enum
|
|
{
|
|
PublicClassesHeaders,
|
|
PublicHeaders,
|
|
PrivateHeaders,
|
|
} CurrentlyProcessing;
|
|
{
|
|
switch( PassIndex )
|
|
{
|
|
case 0:
|
|
default:
|
|
CurrentlyProcessing = PublicClassesHeaders;
|
|
break;
|
|
|
|
case 1:
|
|
CurrentlyProcessing = PublicHeaders;
|
|
break;
|
|
|
|
case 2:
|
|
CurrentlyProcessing = PrivateHeaders;
|
|
break;
|
|
};
|
|
}
|
|
|
|
for (const auto& Module : GManifest.Modules)
|
|
{
|
|
if (Result != ECompilationResult::Succeeded)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// We'll make an ordered list of all UObject headers we care about.
|
|
TArray<FString> UObjectHeaders;
|
|
if( CurrentlyProcessing == PublicClassesHeaders )
|
|
{
|
|
UObjectHeaders = Module.PublicUObjectClassesHeaders;
|
|
}
|
|
// @todo uht: Ideally 'dependson' would not be allowed from public -> private, or NOT at all for new style headers
|
|
else if( CurrentlyProcessing == PublicHeaders )
|
|
{
|
|
// Append the public headers
|
|
UObjectHeaders = Module.PublicUObjectHeaders;
|
|
}
|
|
else
|
|
{
|
|
// Append the private headers
|
|
UObjectHeaders = Module.PrivateUObjectHeaders;
|
|
}
|
|
if( UObjectHeaders.Num() > 0 )
|
|
{
|
|
UPackage* Package = Cast<UPackage>( StaticFindObjectFast( UPackage::StaticClass(), NULL, FName(*Module.LongPackageName), false, false ) );
|
|
if( Package == NULL )
|
|
{
|
|
Package = CreatePackage( NULL, *Module.LongPackageName );
|
|
}
|
|
|
|
// Set some package flags for indicating that this package contains script
|
|
// NOTE: We do this even if we didn't have to create the package, because CoreUObject is compiled into UnrealHeaderTool and we still
|
|
// want to make sure our flags get set
|
|
Package->PackageFlags |= PKG_ContainsScript;
|
|
Package->PackageFlags &= ~(PKG_ClientOptional|PKG_ServerSideOnly);
|
|
Package->PackageFlags |= PKG_Compiling;
|
|
|
|
for (const FString& Filename : UObjectHeaders)
|
|
{
|
|
// Best faith effort at a useful line number for errors occurring during this initial pre-parsing
|
|
int32 ClassDeclLine = -1;
|
|
|
|
#if !PLATFORM_EXCEPTIONS_DISABLED
|
|
try
|
|
#endif
|
|
{
|
|
// Import class.
|
|
const FString ClassName = FPaths::GetBaseFilename(Filename);
|
|
const FString FullModulePath = FPaths::ConvertRelativePathToFull(ModuleInfoPath, Filename);
|
|
|
|
FString HeaderFile;
|
|
if (!FFileHelper::LoadFileToString(HeaderFile, *FullModulePath))
|
|
FError::Throwf(TEXT( "UnrealHeaderTool was unable to load source file '%s'"), *FullModulePath);
|
|
|
|
UClass* ResultClass = GenerateCodeForHeader(Package, *ClassName, RF_Public|RF_Standalone, *HeaderFile, ClassDeclLine);
|
|
GClassSourceFileMap.Add(ResultClass, Filename);
|
|
|
|
if( CurrentlyProcessing == PublicClassesHeaders )
|
|
{
|
|
GPublicClassSet.Add(ResultClass);
|
|
}
|
|
|
|
// Save metadata for the class path, both for it's include path and relative to the module base directory
|
|
if(FullModulePath.StartsWith(Module.BaseDirectory))
|
|
{
|
|
// Get the path relative to the module directory
|
|
const TCHAR *ModuleRelativePath = *FullModulePath + Module.BaseDirectory.Len();
|
|
GClassModuleRelativePathMap.Add(ResultClass, ModuleRelativePath);
|
|
|
|
// Add the include path
|
|
const TCHAR *IncludePath = ModuleRelativePath;
|
|
if(*IncludePath == '/')
|
|
{
|
|
IncludePath++;
|
|
}
|
|
while(*IncludePath != 0 && *IncludePath != '/')
|
|
{
|
|
IncludePath++;
|
|
}
|
|
if(*IncludePath == '/' && *(IncludePath + 1) != 0)
|
|
{
|
|
GClassIncludePathMap.Add(ResultClass, IncludePath + 1);
|
|
}
|
|
}
|
|
}
|
|
#if !PLATFORM_EXCEPTIONS_DISABLED
|
|
catch( TCHAR* ErrorMsg )
|
|
{
|
|
TGuardValue<ELogTimes::Type> DisableLogTimes(GPrintLogTimes, ELogTimes::None);
|
|
|
|
FString Prefix;
|
|
if (ClassDeclLine != -1)
|
|
{
|
|
const FString AbsFilename = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*Filename);
|
|
Prefix = FString::Printf(TEXT("%s(%i): "), *AbsFilename, ClassDeclLine);
|
|
}
|
|
|
|
FString FormattedErrorMessage = FString::Printf(TEXT("%sError: %s\r\n"), *Prefix, ErrorMsg);
|
|
Result = GCompilationResult;
|
|
|
|
UE_LOG(LogCompile, Log, TEXT("%s"), *FormattedErrorMessage);
|
|
GWarn->Log(ELogVerbosity::Error, FormattedErrorMessage);
|
|
|
|
++NumFailures;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if(Result == ECompilationResult::Succeeded && NumFailures != 0)
|
|
{
|
|
Result = ECompilationResult::OtherCompilationError;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Save generated headers
|
|
if ( Result == ECompilationResult::Succeeded )
|
|
{
|
|
// Verify that all script declared superclasses exist.
|
|
for( TObjectIterator<UClass> ItC; ItC; ++ItC )
|
|
{
|
|
const UClass* ScriptClass = *ItC;
|
|
const UClass* ScriptSuperClass = ScriptClass->GetSuperClass();
|
|
|
|
if (ScriptSuperClass && !ScriptSuperClass->HasAnyClassFlags(CLASS_Intrinsic) && GClassStrippedHeaderTextMap.Contains(ScriptClass) && !GClassStrippedHeaderTextMap.Contains(ScriptSuperClass))
|
|
{
|
|
UE_LOG(LogCompile, Error, TEXT("Superclass %s of class %s not found"), *ScriptSuperClass->GetName(), *ScriptClass->GetName());
|
|
Result = ECompilationResult::OtherCompilationError;
|
|
++NumFailures;
|
|
}
|
|
}
|
|
|
|
if (Result == ECompilationResult::Succeeded)
|
|
{
|
|
for (const auto& Module : GManifest.Modules)
|
|
{
|
|
if (UPackage* Package = Cast<UPackage>( StaticFindObjectFast( UPackage::StaticClass(), NULL, FName(*Module.LongPackageName), false, false ) ))
|
|
{
|
|
Result = FHeaderParser::ParseAllHeadersInside(GWarn, Package, Module.SaveExportedHeaders, GManifest.UseRelativePaths);
|
|
if (Result != ECompilationResult::Succeeded)
|
|
{
|
|
++NumFailures;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Avoid TArray slack for meta data.
|
|
GScriptHelper->Shrink();
|
|
|
|
if( bWriteContents )
|
|
{
|
|
UE_LOG(LogCompile, Log, TEXT("********************************* Wrote reference generated code to ReferenceGeneratedCode."));
|
|
}
|
|
else if( bVerifyContents )
|
|
{
|
|
UE_LOG(LogCompile, Log, TEXT("********************************* Wrote generated code to VerifyGeneratedCode and compared to ReferenceGeneratedCode"));
|
|
for (FString& Msg : ChangeMessages)
|
|
{
|
|
UE_LOG(LogCompile, Error, TEXT("%s"), *Msg);
|
|
}
|
|
TArray<FString> RefFileNames;
|
|
IFileManager::Get().FindFiles( RefFileNames, *(FString(FPaths::GameSavedDir()) / TEXT("ReferenceGeneratedCode/*.*")), true, false );
|
|
TArray<FString> VerFileNames;
|
|
IFileManager::Get().FindFiles( VerFileNames, *(FString(FPaths::GameSavedDir()) / TEXT("VerifyGeneratedCode/*.*")), true, false );
|
|
if (RefFileNames.Num() != VerFileNames.Num())
|
|
{
|
|
UE_LOG(LogCompile, Error, TEXT("Number of generated files mismatch ref=%d, ver=%d"), RefFileNames.Num(), VerFileNames.Num());
|
|
}
|
|
}
|
|
TheFlagAudit.WriteResults();
|
|
|
|
delete NameLookupCPP;
|
|
NameLookupCPP = NULL;
|
|
|
|
delete ScriptHelper;
|
|
ScriptHelper = NULL;
|
|
|
|
GIsRequestingExit = true;
|
|
|
|
if(Result == ECompilationResult::Succeeded && NumFailures > 0)
|
|
{
|
|
return ECompilationResult::OtherCompilationError;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
UClass* GenerateCodeForHeader
|
|
(
|
|
UObject* InParent,
|
|
const TCHAR* Name,
|
|
EObjectFlags Flags,
|
|
const TCHAR* Buffer,
|
|
int32& OutClassDeclLine
|
|
)
|
|
{
|
|
// Support for headers without UClasses.
|
|
bool bNonClassHeader = false;
|
|
const TCHAR* InBuffer = Buffer;
|
|
|
|
// Import the script text.
|
|
TArray<FName> DependentOn;
|
|
|
|
// is the parsed class name an interface?
|
|
bool bClassIsAnInterface = false;
|
|
|
|
// Parse the header to extract the information needed
|
|
FStringOutputDevice ClassHeaderTextStrippedOfCppText;
|
|
FString ClassName;
|
|
FString BaseClassName;
|
|
FHeaderParser::SimplifiedClassParse(Buffer, /*out*/ bClassIsAnInterface, /*out*/ DependentOn, /*out*/ ClassName, /*out*/ BaseClassName, /*out*/ OutClassDeclLine, ClassHeaderTextStrippedOfCppText);
|
|
|
|
// In case no UClass is defined, generate the default temporary UClass.
|
|
if (ClassName.IsEmpty())
|
|
{
|
|
ClassName = FString::Printf(TEXT("U%s"), Name);
|
|
BaseClassName = TEXT("UObject");
|
|
bNonClassHeader = true;
|
|
}
|
|
|
|
FString ClassNameStripped = GetClassNameWithPrefixRemoved( *ClassName );
|
|
|
|
// Ensure the base class has any valid prefix and exists as a valid class. Checking for the 'correct' prefix will occur during compilation
|
|
FString BaseClassNameStripped;
|
|
if (!BaseClassName.IsEmpty())
|
|
{
|
|
BaseClassNameStripped = GetClassNameWithPrefixRemoved(BaseClassName);
|
|
if( !FHeaderParser::ClassNameHasValidPrefix(BaseClassName, BaseClassNameStripped) )
|
|
FError::Throwf(TEXT("No prefix or invalid identifier for base class %s.\nClass names must match Unreal prefix specifications (e.g., \"UObject\" or \"AActor\")"), *BaseClassName );
|
|
|
|
if (DependentOn.ContainsByPredicate([&](const FName& Dependency){ FString DependencyStr = Dependency.ToString(); return !DependencyStr.Contains(TEXT(".generated.h")) && FPaths::GetBaseFilename(DependencyStr) == ClassNameStripped; }))
|
|
FError::Throwf(TEXT("Class '%s' contains a dependency (#include or DependsOn) to itself"), *ClassName);
|
|
}
|
|
|
|
//UE_LOG(LogCompile, Log, TEXT("Class: %s extends %s"),*ClassName,*BaseClassName);
|
|
// Handle failure and non-class headers.
|
|
if (BaseClassName.IsEmpty() && (ClassName != TEXT("UObject")))
|
|
{
|
|
FError::Throwf(TEXT("Class '%s' must inherit UObject or a UObject-derived class"), *ClassName );
|
|
}
|
|
|
|
if (ClassName == BaseClassName)
|
|
{
|
|
FError::Throwf(TEXT("Class '%s' cannot inherit from itself"), *ClassName );
|
|
}
|
|
|
|
// In case the file system and the class disagree on the case of the
|
|
// class name replace the fname with the one from the script class file
|
|
// This is needed because not all source control systems respect the
|
|
// original filename's case
|
|
FName ClassNameReplace(*ClassName,FNAME_Replace_Not_Safe_For_Threading);
|
|
|
|
// All classes must start with a valid unreal prefix
|
|
if (!FHeaderParser::ClassNameHasValidPrefix(ClassName, Name) || !FHeaderParser::ClassNameHasValidPrefix(ClassNameReplace.ToString(), Name))
|
|
{
|
|
FError::Throwf(TEXT("Invalid class name '%s'. The class name must match the name of the class header file it is contained in ('%s'), with an appropriate prefix added (A for Actors, U for other classes)"), *ClassNameReplace.ToString(), Name);
|
|
}
|
|
|
|
// Use stripped class name for processing and replace as we did above
|
|
FName ClassNameStrippedReplace(*ClassNameStripped, FNAME_Replace_Not_Safe_For_Threading);
|
|
|
|
UClass* ResultClass = FindObject<UClass>(InParent, *ClassNameStripped);
|
|
|
|
// if we aren't generating headers, then we shouldn't set misaligned object, since it won't get cleared
|
|
|
|
const static bool bVerboseOutput = FParse::Param(FCommandLine::Get(), TEXT("VERBOSE"));
|
|
|
|
// Gracefully update an existing hardcoded class.
|
|
// Note: Most 'native' classes will not have RF_Native set at this point, only classes in Core will go down this path
|
|
if ((ResultClass != NULL) && ResultClass->HasAnyFlags(RF_Native) )
|
|
{
|
|
UClass* SuperClass = ResultClass->GetSuperClass();
|
|
if ((SuperClass != NULL) && (SuperClass->GetName() != BaseClassNameStripped))
|
|
{
|
|
// the code that handles the DependsOn list in the script compiler doesn't work correctly if we manually add the Object class to a class's DependsOn list
|
|
// if Object is also the class's parent. The only way this can happen (since specifying a parent class in a DependsOn statement is a compiler error) is
|
|
// in this block of code, so just handle that here rather than trying to make the script compiler handle this gracefully
|
|
if (BaseClassNameStripped != TEXT("Object"))
|
|
{
|
|
// we're changing the parent of a native class, which may result in the
|
|
// child class being parsed before the new parent class, so add the new
|
|
// parent class to this class's DependsOn() array to guarantee that it
|
|
// will be parsed before this class
|
|
DependentOn.AddUnique(*BaseClassNameStripped);
|
|
}
|
|
|
|
// if the new parent class is an existing native class, attempt to change the parent for this class to the new class
|
|
UClass* NewSuperClass = FindObject<UClass>(ANY_PACKAGE, *BaseClassNameStripped);
|
|
if (NewSuperClass != NULL)
|
|
{
|
|
check(0); // we don't support changing base clases anymore
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// detect if the same class name is used in multiple packages
|
|
if (ResultClass == NULL)
|
|
{
|
|
UClass* ConflictingClass = FindObject<UClass>(ANY_PACKAGE, *ClassNameStripped, true);
|
|
if (ConflictingClass != NULL)
|
|
{
|
|
UE_LOG(LogCompile, Warning, TEXT("Duplicate class name: %s also exists in file %s"), *ClassName, *ConflictingClass->GetOutermost()->GetName());
|
|
}
|
|
}
|
|
|
|
// Create new class.
|
|
ResultClass = new( InParent, *ClassNameStripped, Flags ) UClass( FPostConstructInitializeProperties(),NULL );
|
|
GClassHeaderNameWithNoPathMap.Add(ResultClass, ClassNameStripped);
|
|
|
|
// add CLASS_Interface flag if the class is an interface
|
|
// NOTE: at this pre-parsing/importing stage, we cannot know if our super class is an interface or not,
|
|
// we leave the validation to the main header parser
|
|
if (bClassIsAnInterface)
|
|
{
|
|
ResultClass->ClassFlags |= CLASS_Interface;
|
|
}
|
|
|
|
// In case there actually was no class, mark it as UHT temporary.
|
|
if (bNonClassHeader)
|
|
{
|
|
const FString DummyClassName = FHeaderParser::GenerateTemporaryClassName(*ResultClass->GetName());
|
|
|
|
ResultClass->ClassFlags |= CLASS_Temporary | CLASS_Native;
|
|
ResultClass->Rename(*DummyClassName, NULL, REN_DontCreateRedirectors);
|
|
}
|
|
|
|
// Find or forward-declare base class.
|
|
ResultClass->SetSuperStruct( FindObject<UClass>( InParent, *BaseClassNameStripped ) );
|
|
if (ResultClass->GetSuperStruct() == NULL)
|
|
{
|
|
ResultClass->SetSuperStruct( FindObject<UClass>( ANY_PACKAGE, *BaseClassNameStripped ) );
|
|
}
|
|
|
|
if (ResultClass->GetSuperStruct() == NULL)
|
|
{
|
|
// don't know its parent class yet
|
|
ResultClass->SetSuperStruct( new(InParent, *BaseClassNameStripped) UClass(FPostConstructInitializeProperties(),NULL) );
|
|
}
|
|
|
|
if (ResultClass->GetSuperStruct() != NULL)
|
|
{
|
|
ResultClass->ClassCastFlags |= ResultClass->GetSuperClass()->ClassCastFlags;
|
|
}
|
|
|
|
if ( bVerboseOutput )
|
|
{
|
|
UE_LOG(LogCompile, Log, TEXT("Imported: %s"), *ResultClass->GetFullName() );
|
|
}
|
|
}
|
|
|
|
if (bVerboseOutput)
|
|
{
|
|
for (const auto& Dependency : DependentOn)
|
|
{
|
|
UE_LOG(LogCompile, Log, TEXT("\tAdding %s as a dependency"), *Dependency.ToString());
|
|
}
|
|
}
|
|
|
|
// Set class info.
|
|
GClassStrippedHeaderTextMap.Emplace(ResultClass, MoveTemp(ClassHeaderTextStrippedOfCppText));
|
|
GClassDependentOnMap .Emplace(ResultClass, MoveTemp(DependentOn));
|
|
|
|
return ResultClass;
|
|
}
|