// Copyright Epic Games, Inc. All Rights Reserved. #include "CoreMinimal.h" #include "UnrealHeaderTool.h" #include "Misc/AssertionMacros.h" #include "HAL/PlatformProcess.h" #include "Templates/UnrealTemplate.h" #include "Math/UnrealMathUtility.h" #include "Containers/UnrealString.h" #include "UObject/NameTypes.h" #include "Logging/LogMacros.h" #include "CoreGlobals.h" #include "HAL/FileManager.h" #include "Misc/Parse.h" #include "Misc/CoreMisc.h" #include "Misc/CommandLine.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "Delegates/Delegate.h" #include "Misc/Guid.h" #include "Misc/ConfigCacheIni.h" #include "Misc/FeedbackContext.h" #include "Misc/OutputDeviceNull.h" #include "UObject/ClassTree.h" #include "UObject/Script.h" #include "UObject/ObjectMacros.h" #include "UObject/UObjectGlobals.h" #include "UObject/Class.h" #include "UObject/Package.h" #include "UObject/MetaData.h" #include "UObject/Interface.h" #include "UObject/UnrealType.h" #include "UObject/TextProperty.h" #include "UObject/FieldPathProperty.h" #include "Misc/PackageName.h" #include "UnrealHeaderToolGlobals.h" #include "Exceptions.h" #include "Scope.h" #include "HeaderProvider.h" #include "GeneratedCodeVersion.h" #include "UnrealSourceFile.h" #include "ParserHelper.h" #include "EngineAPI.h" #include "ClassMaps.h" #include "NativeClassExporter.h" #include "ProfilingDebugging/ScopedTimers.h" #include "HeaderParser.h" #include "IScriptGeneratorPluginInterface.h" #include "Manifest.h" #include "StringUtils.h" #include "Features/IModularFeatures.h" #include "Algo/Copy.h" #include "Algo/Sort.h" #include "Algo/Reverse.h" #include "Async/Async.h" #include "Async/ParallelFor.h" #include "Misc/ScopeExit.h" #include "UnrealTypeDefinitionInfo.h" #include "Misc/WildcardString.h" #include "UObject/FieldIterator.h" #include "UObject/FieldPath.h" #include "UObject/WeakFieldPtr.h" #include "Templates/SubclassOf.h" #include ///////////////////////////////////////////////////// // Globals FManifest GManifest; double GMacroizeTime = 0.0; static TArray ChangeMessages; static bool bWriteContents = false; static bool bVerifyContents = false; void ProcessParsedClass(FUnrealClassDefinitionInfo& ClassDef); void ProcessParsedEnum(FUnrealEnumDefinitionInfo& EnumDef); void ProcessParsedStruct(FUnrealScriptStructDefinitionInfo& ScriptStructDef); // Array of all the temporary header async file tasks so we can ensure they have completed before issuing our timings static FGraphEventArray GAsyncFileTasks; // Globals for common class definitions FUnrealClassDefinitionInfo* GUObjectDef = nullptr; FUnrealClassDefinitionInfo* GUClassDef = nullptr; FUnrealClassDefinitionInfo* GUInterfaceDef = nullptr; // Busy wait support for holes in the include graph issues std::atomic GSourcesConcurrent = false; std::atomic GSourcesToParse = 0; std::atomic GSourcesParsing = 0; std::atomic GSourcesCompleted = 0; std::atomic GSourcesStalled = 0; struct FFindDelcarationResults { bool bVirtualFound = false; const FDeclaration* Declaration = nullptr; int FunctionNameTokenIndex = -1; bool WasFound() const { return Declaration != nullptr; } bool IsVirtual() const { return bVirtualFound; } }; FFindDelcarationResults FindDeclaration(const FUnrealStructDefinitionInfo& StructDef, const FString& Identifier); namespace { static const FName NAME_SerializeToFArchive("SerializeToFArchive"); static const FName NAME_SerializeToFStructuredArchive("SerializeToFStructuredArchive"); static const FName NAME_ObjectInitializerConstructorDeclared("ObjectInitializerConstructorDeclared"); static const FName NAME_NoGetter("NoGetter"); static const FName NAME_GetByRef("GetByRef"); static const FString STRING_StructPackage(TEXT("StructPackage")); static const int32 HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX_LENGTH = FString(HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX).Len(); static FString AsTEXT(const FString& InStr) { return FString::Printf(TEXT("TEXT(\"%s\")"), *InStr); } const TCHAR* HeaderCopyright = TEXT("// Copyright Epic Games, Inc. All Rights Reserved.\r\n" "/*===========================================================================\r\n" "\tGenerated code exported from UnrealHeaderTool.\r\n" "\tDO NOT modify this manually! Edit the corresponding .h files instead!\r\n" "===========================================================================*/\r\n" LINE_TERMINATOR_ANSI); const TCHAR* RequiredCPPIncludes = TEXT("#include \"UObject/GeneratedCppIncludes.h\"" LINE_TERMINATOR_ANSI); const TCHAR* EnableDeprecationWarnings = TEXT("PRAGMA_ENABLE_DEPRECATION_WARNINGS" LINE_TERMINATOR_ANSI); const TCHAR* DisableDeprecationWarnings = TEXT("PRAGMA_DISABLE_DEPRECATION_WARNINGS" LINE_TERMINATOR_ANSI); // A struct which emits #if and #endif blocks as appropriate when invoked. struct FMacroBlockEmitter { explicit FMacroBlockEmitter(FOutputDevice& InOutput, const TCHAR* InMacro) : Output(InOutput) , bEmittedIf(false) , Macro(InMacro) { } ~FMacroBlockEmitter() { if (bEmittedIf) { Output.Logf(TEXT("#endif // %s\r\n"), Macro); } } void operator()(bool bInBlock) { if (!bEmittedIf && bInBlock) { Output.Logf(TEXT("#if %s\r\n"), Macro); bEmittedIf = true; } else if (bEmittedIf && !bInBlock) { Output.Logf(TEXT("#endif // %s\r\n"), Macro); bEmittedIf = false; } } FMacroBlockEmitter(const FMacroBlockEmitter&) = delete; FMacroBlockEmitter& operator=(const FMacroBlockEmitter&) = delete; private: FOutputDevice& Output; bool bEmittedIf; const TCHAR* Macro; }; /** Guard that should be put at the start editor only generated code */ const auto& BeginEditorOnlyGuard = TEXT("#if WITH_EDITOR" LINE_TERMINATOR_ANSI); /** Guard that should be put at the end of editor only generated code */ const auto& EndEditorOnlyGuard = TEXT("#endif //WITH_EDITOR" LINE_TERMINATOR_ANSI); /** Whether or not the given class has any replicated properties. */ static bool ClassHasReplicatedProperties(const FUnrealClassDefinitionInfo& ClassDef) { if (!ClassDef.HasAnyClassFlags(CLASS_ReplicationDataIsSetUp)) { for (TSharedRef PropertyDef : ClassDef.GetProperties()) { if (PropertyDef->HasAnyPropertyFlags(CPF_Net)) { return true; } } } return ClassDef.HasOwnedClassReps(); } static void ExportNetData(FOutputDevice& Out, const FUnrealClassDefinitionInfo& ClassDef, const TCHAR* API) { const TArray& ClassReps = ClassDef.GetClassReps(); FUHTStringBuilder NetFieldBuilder; NetFieldBuilder.Logf(TEXT("" "\tenum class ENetFields_Private : uint16\r\n" "\t{\r\n" "\t\tNETFIELD_REP_START=(uint16)((int32)Super::ENetFields_Private::NETFIELD_REP_END + (int32)1),\r\n")); FUHTStringBuilder ArrayDimBuilder; bool bAnyStaticArrays = false; bool bIsFirst = true; for (int32 ClassRepIndex = ClassDef.GetFirstOwnedClassRep(); ClassRepIndex < ClassReps.Num(); ++ClassRepIndex) { const FUnrealPropertyDefinitionInfo* PropertyDef = ClassReps[ClassRepIndex]; const FString PropertyName = PropertyDef->GetName(); if (!PropertyDef->IsStaticArray()) { if (UNLIKELY(bIsFirst)) { NetFieldBuilder.Logf(TEXT("\t\t%s=NETFIELD_REP_START,\r\n"), *PropertyName); bIsFirst = false; } else { NetFieldBuilder.Logf(TEXT("\t\t%s,\r\n"), *PropertyName); } } else { bAnyStaticArrays = true; ArrayDimBuilder.Logf(TEXT("\t\t%s=%s,\r\n"), *PropertyName, PropertyDef->GetArrayDimensions()); if (UNLIKELY(bIsFirst)) { NetFieldBuilder.Logf(TEXT("\t\t%s_STATIC_ARRAY=NETFIELD_REP_START,\r\n"), *PropertyName); bIsFirst = false; } else { NetFieldBuilder.Logf(TEXT("\t\t%s_STATIC_ARRAY,\r\n"), *PropertyName); } NetFieldBuilder.Logf(TEXT("\t\t%s_STATIC_ARRAY_END=((uint16)%s_STATIC_ARRAY + (uint16)EArrayDims_Private::%s - (uint16)1),\r\n"), *PropertyName, *PropertyName, *PropertyName); } } const FUnrealPropertyDefinitionInfo* LastPropertyDef = ClassReps.Last(); NetFieldBuilder.Logf(TEXT("\t\tNETFIELD_REP_END=%s%s"), *LastPropertyDef->GetName(), LastPropertyDef->IsStaticArray() ? TEXT("_STATIC_ARRAY_END") : TEXT("")); NetFieldBuilder.Log(TEXT("\t};")); if (bAnyStaticArrays) { Out.Logf(TEXT("" "\tenum class EArrayDims_Private : uint16\r\n" "\t{\r\n" "%s" "\t};\r\n"), *ArrayDimBuilder); } Out.Logf(TEXT("" "%s\r\n" // NetFields "\t%s_API virtual void ValidateGeneratedRepEnums(const TArray& ClassReps) const override;\r\n"), *NetFieldBuilder, API); } static const FString STRING_GetLifetimeReplicatedPropsStr(TEXT("GetLifetimeReplicatedProps")); static void WriteReplicatedMacroData( const TCHAR* ClassCPPName, const TCHAR* API, FUnrealClassDefinitionInfo& ClassDef, FOutputDevice& Writer, const FUnrealSourceFile& SourceFile, EExportClassOutFlags& OutFlags) { const bool bHasGetLifetimeReplicatedProps = FindDeclaration(ClassDef, STRING_GetLifetimeReplicatedPropsStr).WasFound(); if (!bHasGetLifetimeReplicatedProps) { // Default version autogenerates declarations. if (ClassDef.GetGeneratedCodeVersion() == EGeneratedCodeVersion::V1) { Writer.Logf(TEXT("\tvoid GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override;\r\n")); } else { ClassDef.Throwf(TEXT("Class %s has Net flagged properties and should declare member function: void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override"), ClassCPPName); } } ExportNetData(Writer, ClassDef, API); // If this class has replicated properties and it owns the first one, that means // it's the base most replicated class. In that case, go ahead and add our interface macro. if (ClassDef.HasClassReps() && ClassDef.GetFirstOwnedClassRep() == 0) { OutFlags |= EExportClassOutFlags::NeedsPushModelHeaders; Writer.Logf(TEXT( "private:\r\n" "\tREPLICATED_BASE_CLASS(%s%s)\r\n" "public:\r\n" ), ClassDef.GetPrefixCPP(), *ClassDef.GetName()); } } } void FGeneratedFileInfo::StartLoad(FString&& InFilename) { ensureMsgf(Filename.IsEmpty(), TEXT("FPreloadHeaderFileInfo::StartLoad called twice with different paths.")); Filename = MoveTemp(InFilename); if (bAllowSaveExportedHeaders) { auto LoadFileContentsTask = [this]() { SCOPE_SECONDS_COUNTER_UHT(LoadHeaderContentFromFile); FFileHelper::LoadFileToString(OriginalContents, *Filename); }; LoadTaskRef = FFunctionGraphTask::CreateAndDispatchWhenReady(MoveTemp(LoadFileContentsTask), TStatId()); } } void FGeneratedFileInfo::Load(FString&& InFilename) { ensureMsgf(Filename.IsEmpty(), TEXT("FPreloadHeaderFileInfo::StartLoad called twice with different paths.")); Filename = MoveTemp(InFilename); if (bAllowSaveExportedHeaders) { SCOPE_SECONDS_COUNTER_UHT(LoadHeaderContentFromFile); FFileHelper::LoadFileToString(OriginalContents, *Filename); } } void FGeneratedFileInfo::GenerateBodyHash() { GeneratedBodyHash = GenerateTextHash(*GeneratedBody); } FGeneratedCPP::FGeneratedCPP(FUnrealPackageDefinitionInfo& InPackageDef, FUnrealSourceFile& InSourceFile) : PackageDef(InPackageDef) , SourceFile(InSourceFile) , Header(InPackageDef.GetModule().SaveExportedHeaders) , Source(InPackageDef.GetModule().SaveExportedHeaders) { } void FGeneratedCPP::AddGenerateTaskRef(FGraphEventArray& Events) const { check(GenerateTaskRef.IsValid() || !SourceFile.ShouldExport()); if (GenerateTaskRef.IsValid()) { Events.Add(GenerateTaskRef); } } void FGeneratedCPP::AddExportTaskRef(FGraphEventArray& Events) const { check(ExportTaskRef.IsValid() || !SourceFile.ShouldExport()); if (ExportTaskRef.IsValid()) { Events.Add(ExportTaskRef); } } #define BEGIN_WRAP_EDITOR_ONLY(DoWrap) DoWrap ? BeginEditorOnlyGuard : TEXT("") #define END_WRAP_EDITOR_ONLY(DoWrap) DoWrap ? EndEditorOnlyGuard : TEXT("") FFindDelcarationResults FindDeclaration(const FUnrealStructDefinitionInfo& StructDef, const FString& Identifier) { FFindDelcarationResults Results; if (Identifier.IsEmpty()) { return Results; } for (const FDeclaration& Declaration : StructDef.GetDeclarations()) { for (int32 Index = 0, EIndex = Declaration.Tokens.Num(); Index != EIndex; ++Index) { const FToken& Token = Declaration.Tokens[Index]; if (Token.IsIdentifier()) { if (Token.IsValue(TEXT("virtual"), ESearchCase::CaseSensitive)) { Results.bVirtualFound = true; } else if (Token.IsValue(*Identifier, ESearchCase::CaseSensitive)) { Results.Declaration = &Declaration; Results.FunctionNameTokenIndex = Index; return Results; } } } } return Results; } void ConvertToBuildIncludePath(const FManifestModule& Module, FString& LocalPath) { FPaths::MakePathRelativeTo(LocalPath, *Module.IncludeBase); } FString Macroize(const TCHAR* MacroName, FString&& StringToMacroize) { FScopedDurationTimer Tracker(GMacroizeTime); FString Result(MoveTemp(StringToMacroize)); if (Result.Len()) { Result.ReplaceInline(TEXT("\r\n"), TEXT("\n"), ESearchCase::CaseSensitive); Result.ReplaceInline(TEXT("\n"), TEXT(" \\\n"), ESearchCase::CaseSensitive); checkSlow(Result.EndsWith(TEXT(" \\\n"), ESearchCase::CaseSensitive)); if (Result.Len() >= 3) { for (int32 Index = Result.Len() - 3; Index < Result.Len(); ++Index) { Result[Index] = TEXT('\n'); } } else { Result = TEXT("\n\n\n"); } Result.ReplaceInline(TEXT("\n"), TEXT("\r\n"), ESearchCase::CaseSensitive); } return FString::Printf(TEXT("#define %s%s\r\n%s"), MacroName, Result.Len() ? TEXT(" \\") : TEXT(""), *Result); } struct FParmsAndReturnProperties { bool HasParms() const { return Parms.Num() || Return; } TArray Parms; FUnrealPropertyDefinitionInfo* Return = nullptr; }; /** * 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(FUnrealFunctionDefinitionInfo& FunctionDef) { FParmsAndReturnProperties Result; for (TSharedRef PropertyDef : FunctionDef.GetProperties()) { if (PropertyDef->HasSpecificPropertyFlags(CPF_Parm | CPF_ReturnParm, CPF_Parm)) { Result.Parms.Add(&*PropertyDef); } else if (PropertyDef->HasAnyPropertyFlags(CPF_ReturnParm)) { Result.Return = &*PropertyDef; } } 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 ShouldExportFunction(FUnrealFunctionDefinitionInfo& FunctionDef) { // export any script stubs for native functions declared in interface classes bool bIsBlueprintNativeEvent = FunctionDef.HasAllFunctionFlags(FUNC_BlueprintEvent | FUNC_Native); if (FunctionDef.GetOwnerClass()->HasAnyClassFlags(CLASS_Interface) && !bIsBlueprintNativeEvent) { return true; } // always export if the function is static if (FunctionDef.HasAnyFunctionFlags(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 (FUnrealFunctionDefinitionInfo* ParentFunctionDef = FunctionDef.GetSuperFunction(); ParentFunctionDef; ParentFunctionDef = ParentFunctionDef->GetSuperFunction()) { if (ParentFunctionDef->HasAnyFunctionFlags(FUNC_Native)) { return false; } } return true; } FString CreateLiteralString(const FString& Str) { FString Result; // Have a reasonable guess at reserving the right size Result.Reserve(Str.Len() + 8); Result += TEXT("TEXT(\""); 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; } FString CreateUTF8LiteralString(const FString& Str) { FString Result; // Have a reasonable guess at reserving the right size Result.Reserve(Str.Len() + 2); Result += TEXT("\""); bool bPreviousCharacterWasHex = false; FTCHARToUTF8 StrUTF8(*Str); const char* Ptr = StrUTF8.Get(); while (char Ch = *Ptr++) { switch (Ch) { case '\r': continue; case '\n': Result += TEXT("\\n"); bPreviousCharacterWasHex = false; break; case '\\': Result += TEXT("\\\\"); bPreviousCharacterWasHex = false; break; case '\"': Result += TEXT("\\\""); bPreviousCharacterWasHex = false; break; default: if (Ch < 31) { Result += FString::Printf(TEXT("\\x%02x"), (uint8)Ch); bPreviousCharacterWasHex = true; } else { // We close and open the literal 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 += "\"\""; } bPreviousCharacterWasHex = false; Result += Ch; } break; } } Result += TEXT("\""); return Result; } // Returns the METADATA_PARAMS for this output static FString OutputMetaDataCodeForObject(FOutputDevice& OutDeclaration, FOutputDevice& Out, FUnrealTypeDefinitionInfo& TypeDef, const TCHAR* MetaDataBlockName, const TCHAR* DeclSpaces, const TCHAR* Spaces) { TMap MetaData = TypeDef.GenerateMetadataMap(); FString Result; if (MetaData.Num()) { typedef TKeyValuePair KVPType; TArray KVPs; KVPs.Reserve(MetaData.Num()); for (TPair& 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 Algo::SortBy(KVPs, &KVPType::Key, FNameLexicalLess()); FString MetaDataBlockNameWithoutScope = MetaDataBlockName; int32 ScopeIndex = MetaDataBlockNameWithoutScope.Find(TEXT("::"), ESearchCase::CaseSensitive); if (ScopeIndex != INDEX_NONE) { MetaDataBlockNameWithoutScope.RightChopInline(ScopeIndex + 2, false); } OutDeclaration.Log (TEXT("#if WITH_METADATA\r\n")); OutDeclaration.Logf(TEXT("%sstatic const UECodeGen_Private::FMetaDataPairParam %s[];\r\n"), DeclSpaces, *MetaDataBlockNameWithoutScope); OutDeclaration.Log (TEXT("#endif\r\n")); Out.Log (TEXT("#if WITH_METADATA\r\n")); Out.Logf(TEXT("%sconst UECodeGen_Private::FMetaDataPairParam %s[] = {\r\n"), Spaces, MetaDataBlockName); for (const KVPType& KVP : KVPs) { Out.Logf(TEXT("%s\t{ %s, %s },\r\n"), Spaces, *CreateUTF8LiteralString(KVP.Key.ToString()), *CreateUTF8LiteralString(*KVP.Value)); } Out.Logf(TEXT("%s};\r\n"), Spaces); Out.Log (TEXT("#endif\r\n")); Result = FString::Printf(TEXT("METADATA_PARAMS(%s, UE_ARRAY_COUNT(%s))"), MetaDataBlockName, MetaDataBlockName); } else { Result = TEXT("METADATA_PARAMS(nullptr, 0)"); } return Result; } void FNativeClassHeaderGenerator::ExportProperties(FOutputDevice& Out, FUnrealStructDefinitionInfo& StructDef, int32 TextIndent) { FMacroBlockEmitter WithEditorOnlyData(Out, TEXT("WITH_EDITORONLY_DATA")); // Iterate over all properties in this struct. for (TSharedRef PropertyDef : StructDef.GetProperties()) { WithEditorOnlyData(PropertyDef->IsEditorOnlyProperty()); // Export property specifiers // Indent code and export CPP text. FUHTStringBuilder JustPropertyDecl; PropertyDef->ExportCppDeclaration( JustPropertyDecl, EExportedDeclaration::Member, PropertyDef->GetArrayDimensions()); ApplyAlternatePropertyExportText(*PropertyDef, JustPropertyDecl, EExportingState::TypeEraseDelegates); // Finish up line. Out.Logf(TEXT("%s%s;\r\n"), FCString::Tab(TextIndent + 1), *JustPropertyDecl); } } static FString GNullPtr(TEXT("nullptr")); const FString& FNativeClassHeaderGenerator::GetPackageSingletonName(FUnrealPackageDefinitionInfo& PackageDef, TSet* UniqueCrossModuleReferences) { PackageDef.AddCrossModuleReference(UniqueCrossModuleReferences); return PackageDef.GetSingletonName(); } const FString& FNativeClassHeaderGenerator::GetPackageSingletonNameFuncAddr(FUnrealPackageDefinitionInfo& PackageDef, TSet* UniqueCrossModuleReferences) { PackageDef.AddCrossModuleReference(UniqueCrossModuleReferences); return PackageDef.GetSingletonNameChopped(); } const FString& FNativeClassHeaderGenerator::GetSingletonNameFuncAddr(FUnrealFieldDefinitionInfo* FieldDef, TSet* UniqueCrossModuleReferences, bool bRequiresValidObject) { if (!FieldDef) { return GNullPtr; } else { FieldDef->AddCrossModuleReference(UniqueCrossModuleReferences, bRequiresValidObject); return FieldDef->GetSingletonNameChopped(bRequiresValidObject); } } void FNativeClassHeaderGenerator::GetPropertyTag(FUHTStringBuilder& Out, FUnrealPropertyDefinitionInfo& PropDef) { const FPropertyBase& PropertyBase = PropDef.GetPropertyBase(); // Do the value types switch (PropertyBase.GetUHTPropertyType()) { #if UHT_ENABLE_VALUE_PROPERTY_TAG case EUHTPropertyType::Enum: PropertyBase.EnumDef->GetHashTag(PropDef, Out); break; case EUHTPropertyType::Struct: PropertyBase.ScriptStructDef->GetHashTag(PropDef, Out); break; #endif #if UHT_ENABLE_PTR_PROPERTY_TAG case EUHTPropertyType::ObjectReference: case EUHTPropertyType::WeakObjectReference: case EUHTPropertyType::LazyObjectReference: case EUHTPropertyType::SoftObjectReference: case EUHTPropertyType::ObjectPtrReference: case EUHTPropertyType::InterfaceReference: case EUHTPropertyType::InterfaceReference: PropertyBase.ClassDef->GetHashTag(Out); break; #endif #if UHT_ENABLE_DELEGATE_PROPERTY_TAG case EUHTPropertyType::Delegate: case EUHTPropertyType::MulticastDelegate: PropertyBase.FunctionDef->GetHashTag(PropDef, Out); break; #endif default: break; } // Add the key value if (PropDef.HasKeyPropDef()) { GetPropertyTag(Out, PropDef.GetKeyPropDef()); } if (PropDef.HasValuePropDef()) { GetPropertyTag(Out, PropDef.GetValuePropDef()); } } void FNativeClassHeaderGenerator::OutputProperty(FOutputDevice& DeclOut, FOutputDevice& Out, FReferenceGatherers& OutReferenceGatherers, const TCHAR* Scope, TArray& PropertyNamesAndPointers, FUnrealPropertyDefinitionInfo& PropertyDef, const TCHAR* OffsetStr, FString&& Name, const TCHAR* DeclSpaces, const TCHAR* Spaces, const TCHAR* SourceStruct) const { const FPropertyBase& PropertyBase = PropertyDef.GetPropertyBase(); FString PropName = CreateUTF8LiteralString(PropertyDef.GetName()); FString PropNameDep = PropertyDef.HasAllPropertyFlags(CPF_Deprecated) ? PropertyDef.GetName() + TEXT("_DEPRECATED") : PropertyDef.GetName(); const TCHAR* FPropertyObjectFlags = TEXT("RF_Public|RF_Transient|RF_MarkAsNative"); EPropertyFlags PropFlags = PropertyDef.GetPropertyFlags() & ~CPF_ComputedFlags; FUHTStringBuilder PropTag; GetPropertyTag(PropTag, PropertyDef); FString PropNotifyFunc = PropertyDef.GetRepNotifyFunc() != NAME_None ? CreateUTF8LiteralString(*PropertyDef.GetRepNotifyFunc().ToString()) : TEXT("nullptr"); FString ArrayDim = PropertyDef.IsStaticArray() ? FString::Printf(TEXT("CPP_ARRAY_DIM(%s, %s)"), *PropNameDep, SourceStruct) : TEXT("1"); FString NameWithoutScope = *Name; { //FString Scope; int32 ScopeIndex = NameWithoutScope.Find(TEXT("::"), ESearchCase::CaseSensitive); if (ScopeIndex != INDEX_NONE) { //Scope = NameWithoutScope.Left(ScopeIndex) + TEXT("_"); NameWithoutScope.RightChopInline(ScopeIndex + 2, false); } } auto OutputByteProperty = [&]() { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FBytePropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FBytePropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Byte, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *GetSingletonNameFuncAddr(PropertyBase.EnumDef, OutReferenceGatherers.UniqueCrossModuleReferences), *MetaDataParams, *PropTag ); }; switch (PropertyBase.GetUHTPropertyType()) { case EUHTPropertyType::Byte: { OutputByteProperty(); break; } case EUHTPropertyType::Int8: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FInt8PropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FInt8PropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Int8, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::Int16: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FInt16PropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FInt16PropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Int16, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::Int: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); const TCHAR* PropTypeName = PropertyDef.IsUnsized() ? TEXT("FUnsizedIntPropertyParams") : TEXT("FIntPropertyParams"); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::%s %s;\r\n"), DeclSpaces, PropTypeName, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::%s %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Int, %s, %s, %s, %s };%s\r\n"), Spaces, PropTypeName, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::Int64: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FInt64PropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FInt64PropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Int64, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::UInt16: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FFInt16PropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FFInt16PropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::UInt16, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::UInt32: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); const TCHAR* PropTypeName = PropertyDef.IsUnsized() ? TEXT("FUnsizedFIntPropertyParams") : TEXT("FUInt32PropertyParams"); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::%s %s;\r\n"), DeclSpaces, PropTypeName, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::%s %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::UInt32, %s, %s, %s, %s };%s\r\n"), Spaces, PropTypeName, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::UInt64: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FFInt64PropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FFInt64PropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::UInt64, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::Float: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FFloatPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FFloatPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Float, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::Double: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FDoublePropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FDoublePropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Double, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::LargeWorldCoordinatesReal: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FLargeWorldCoordinatesRealPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FLargeWorldCoordinatesRealPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::LargeWorldCoordinatesReal, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::Bool: case EUHTPropertyType::Bool8: case EUHTPropertyType::Bool16: case EUHTPropertyType::Bool32: case EUHTPropertyType::Bool64: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); bool bIsNativeBool = PropertyBase.Type == CPT_Bool || EnumHasAnyFlags(PropertyBase.PropertyFlags, CPF_ReturnParm); FString OuterSize; FString Setter; if (UHTCast(PropertyDef.GetOuter()) == nullptr) { OuterSize = TEXT("0"); Setter = TEXT("nullptr"); } else { OuterSize = FString::Printf(TEXT("sizeof(%s)"), SourceStruct); DeclOut.Logf(TEXT("%sstatic void %s_SetBit(void* Obj);\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf(TEXT("%svoid %s_SetBit(void* Obj)\r\n"), Spaces, *Name); Out.Logf(TEXT("%s{\r\n"), Spaces); Out.Logf(TEXT("%s\t((%s*)Obj)->%s%s = 1;\r\n"), Spaces, SourceStruct, *PropertyDef.GetName(), PropertyDef.HasAllPropertyFlags(CPF_Deprecated) ? TEXT("_DEPRECATED") : TEXT("")); Out.Logf(TEXT("%s}\r\n"), Spaces); Setter = FString::Printf(TEXT("&%s_SetBit"), *Name); } DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FBoolPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FBoolPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Bool %s, %s, %s, sizeof(%s), %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, bIsNativeBool ? TEXT("| UECodeGen_Private::EPropertyGenFlags::NativeBool") : TEXT(""), FPropertyObjectFlags, *ArrayDim, *PropertyDef.GetCPPType(nullptr, 0), *OuterSize, *Setter, *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::ObjectReference: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); if (PropertyBase.ClassDef->IsChildOf(*GUClassDef)) { DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FClassPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FClassPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Class, %s, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *GetSingletonNameFuncAddr(PropertyBase.ClassDef, OutReferenceGatherers.UniqueCrossModuleReferences, false), *GetSingletonNameFuncAddr(PropertyBase.MetaClassDef, OutReferenceGatherers.UniqueCrossModuleReferences, false), *MetaDataParams, *PropTag ); } else { DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FObjectPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FObjectPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Object, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *GetSingletonNameFuncAddr(PropertyBase.ClassDef, OutReferenceGatherers.UniqueCrossModuleReferences, false), *MetaDataParams, *PropTag ); } break; } case EUHTPropertyType::ObjectPtrReference: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); if (PropertyBase.ClassDef->IsChildOf(*GUClassDef)) { DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FClassPtrPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FClassPtrPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Class | UECodeGen_Private::EPropertyGenFlags::ObjectPtr, %s, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *GetSingletonNameFuncAddr(PropertyBase.ClassDef, OutReferenceGatherers.UniqueCrossModuleReferences, false), *GetSingletonNameFuncAddr(PropertyBase.MetaClassDef, OutReferenceGatherers.UniqueCrossModuleReferences, false), *MetaDataParams, *PropTag ); } else { DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FObjectPtrPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FObjectPtrPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Object | UECodeGen_Private::EPropertyGenFlags::ObjectPtr, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *GetSingletonNameFuncAddr(PropertyBase.ClassDef, OutReferenceGatherers.UniqueCrossModuleReferences, false), *MetaDataParams, *PropTag ); } break; } case EUHTPropertyType::SoftObjectReference: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); if (PropertyBase.ClassDef->IsChildOf(*GUClassDef)) { DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FSoftClassPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FSoftClassPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::SoftClass, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *GetSingletonNameFuncAddr(PropertyBase.MetaClassDef, OutReferenceGatherers.UniqueCrossModuleReferences, false), *MetaDataParams, *PropTag ); } else { DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FSoftObjectPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FSoftObjectPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::SoftObject, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *GetSingletonNameFuncAddr(PropertyBase.ClassDef, OutReferenceGatherers.UniqueCrossModuleReferences, false), *MetaDataParams, *PropTag ); } break; } case EUHTPropertyType::WeakObjectReference: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FWeakObjectPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FWeakObjectPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::WeakObject, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *GetSingletonNameFuncAddr(PropertyBase.ClassDef, OutReferenceGatherers.UniqueCrossModuleReferences, false), *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::LazyObjectReference: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FLazyObjectPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FLazyObjectPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::LazyObject, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *GetSingletonNameFuncAddr(PropertyBase.ClassDef, OutReferenceGatherers.UniqueCrossModuleReferences, false), *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::Interface: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FInterfacePropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FInterfacePropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Interface, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *GetSingletonNameFuncAddr(PropertyBase.ClassDef, OutReferenceGatherers.UniqueCrossModuleReferences, false), *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::Name: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FNamePropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FNamePropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Name, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::String: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FStrPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FStrPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Str, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::DynamicArray: { FUnrealPropertyDefinitionInfo& ValueDef = PropertyDef.GetValuePropDef(); FString ValueVariableName = FString::Printf(TEXT("%sNewProp_%s_Inner"), Scope, *ValueDef.GetName()); OutputProperty(DeclOut, Out, OutReferenceGatherers, Scope, PropertyNamesAndPointers, ValueDef, TEXT("0"), MoveTemp(ValueVariableName), DeclSpaces, Spaces, nullptr); FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FArrayPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FArrayPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Array, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, PropertyDef.GetAllocatorType() == EAllocatorType::MemoryImage ? TEXT("EArrayPropertyFlags::UsesMemoryImageAllocator") : TEXT("EArrayPropertyFlags::None"), *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::Map: { FUnrealPropertyDefinitionInfo& KeyDef = PropertyDef.GetKeyPropDef(); FUnrealPropertyDefinitionInfo& ValueDef = PropertyDef.GetValuePropDef(); FString KeyVariableName = FString::Printf(TEXT("%sNewProp_%s_KeyProp"), Scope, *KeyDef.GetName()); FString ValueVariableName = FString::Printf(TEXT("%sNewProp_%s_ValueProp"), Scope, *ValueDef.GetName()); OutputProperty(DeclOut, Out, OutReferenceGatherers, Scope, PropertyNamesAndPointers, ValueDef, TEXT("1"), MoveTemp(ValueVariableName), DeclSpaces, Spaces, nullptr); OutputProperty(DeclOut, Out, OutReferenceGatherers, Scope, PropertyNamesAndPointers, KeyDef, TEXT("0"), MoveTemp(KeyVariableName), DeclSpaces, Spaces, nullptr); FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FMapPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FMapPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Map, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, PropertyDef.GetAllocatorType() == EAllocatorType::MemoryImage ? TEXT("EMapPropertyFlags::UsesMemoryImageAllocator") : TEXT("EMapPropertyFlags::None"), *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::Set: { FUnrealPropertyDefinitionInfo& ValueDef = PropertyDef.GetValuePropDef(); FString ValueVariableName = FString::Printf(TEXT("%sNewProp_%s_ElementProp"), Scope, *ValueDef.GetName()); OutputProperty(DeclOut, Out, OutReferenceGatherers, Scope, PropertyNamesAndPointers, ValueDef, TEXT("0"), MoveTemp(ValueVariableName), DeclSpaces, Spaces, nullptr); FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FSetPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); if (ValueDef.IsStructOrStructStaticArray()) { const FString& StructName = ValueDef.GetPropertyBase().ScriptStructDef->GetNameCPP(); Out.Logf(TEXT("%sstatic_assert(TModels::Value, \"The structure '%s' is used in a TSet but does not have a GetValueTypeHash defined\");\r\n"), Spaces, *StructName, *StructName); } Out.Logf( TEXT("%sconst UECodeGen_Private::FSetPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Set, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::Struct: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FStructPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FStructPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Struct, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *GetSingletonNameFuncAddr(PropertyBase.ScriptStructDef, OutReferenceGatherers.UniqueCrossModuleReferences), *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::Delegate: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FDelegatePropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FDelegatePropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Delegate, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *GetSingletonNameFuncAddr(PropertyBase.FunctionDef, OutReferenceGatherers.UniqueCrossModuleReferences), *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::MulticastDelegate: { bool bIsSparse = PropertyBase.FunctionDef->GetFunctionType() == EFunctionType::SparseDelegate; FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FMulticastDelegatePropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FMulticastDelegatePropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::%sMulticastDelegate, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, !bIsSparse ? TEXT("Inline") : TEXT("Sparse"), FPropertyObjectFlags, *ArrayDim, OffsetStr, *GetSingletonNameFuncAddr(PropertyBase.FunctionDef, OutReferenceGatherers.UniqueCrossModuleReferences), *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::Text: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FTextPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FTextPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Text, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *MetaDataParams, *PropTag ); break; } case EUHTPropertyType::Enum: { if (PropertyBase.EnumDef->GetCppForm() != UEnum::ECppForm::EnumClass) { OutputByteProperty(); } else { // Output the underlying property FString PropVarName = FString::Printf(TEXT("%s_Underlying"), *Name); OutputProperty(DeclOut, Out, OutReferenceGatherers, Scope, PropertyNamesAndPointers, PropertyDef.GetValuePropDef(), TEXT("0"), *PropVarName, DeclSpaces, Spaces, nullptr); FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FEnumPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FEnumPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::Enum, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *GetSingletonNameFuncAddr(PropertyBase.EnumDef, OutReferenceGatherers.UniqueCrossModuleReferences), *MetaDataParams, *PropTag ); } break; } case EUHTPropertyType::FieldPath: { FString MetaDataParams = OutputMetaDataCodeForObject(DeclOut, Out, PropertyDef, *FString::Printf(TEXT("%s_MetaData"), *Name), DeclSpaces, Spaces); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FFieldPathPropertyParams %s;\r\n"), DeclSpaces, *NameWithoutScope); Out.Logf( TEXT("%sconst UECodeGen_Private::FFieldPathPropertyParams %s = { %s, %s, (EPropertyFlags)0x%016llx, UECodeGen_Private::EPropertyGenFlags::FieldPath, %s, %s, %s, %s, %s };%s\r\n"), Spaces, *Name, *PropName, *PropNotifyFunc, PropFlags, FPropertyObjectFlags, *ArrayDim, OffsetStr, *FString::Printf(TEXT("&F%s::StaticClass"), *PropertyBase.FieldClassName.ToString()), *MetaDataParams, *PropTag ); break; } default: check(false); } PropertyNamesAndPointers.Emplace(MoveTemp(Name), PropertyDef); } bool IsEditorOnlyDataProperty(FUnrealPropertyDefinitionInfo& PropDef) { for (FUnrealPropertyDefinitionInfo* TestPropDef = &PropDef; TestPropDef; TestPropDef = UHTCast(TestPropDef->GetOuter())) { if (TestPropDef->IsEditorOnlyProperty()) { return true; } } return false; } FString GetEventStructParamsName(FUnrealObjectDefinitionInfo& OuterDef, const TCHAR* FunctionName) { FString OuterName; if (FUnrealClassDefinitionInfo* ClassDef = UHTCast(OuterDef)) { OuterName = ClassDef->GetName(); } else if (FUnrealPackageDefinitionInfo* PackageDef = UHTCast(OuterDef)) { OuterName = PackageDef->GetPackage()->GetName(); OuterName.ReplaceInline(TEXT("/"), TEXT("_"), ESearchCase::CaseSensitive); } else { OuterDef.Throwf(TEXT("Unrecognized outer type")); } FString Result = FString::Printf(TEXT("%s_event%s_Parms"), *OuterName, FunctionName); if (Result.Len() && FChar::IsDigit(Result[0])) { Result.InsertAt(0, TCHAR('_')); } return Result; } TTuple FNativeClassHeaderGenerator::OutputProperties(FOutputDevice& DeclOut, FOutputDevice& Out, FReferenceGatherers& OutReferenceGatherers, const TCHAR* Scope, FUnrealStructDefinitionInfo& StructDef, const TCHAR* DeclSpaces, const TCHAR* Spaces) const { if (StructDef.GetProperties().Num() == 0) { return TTuple(TEXT("nullptr"), TEXT("0")); } TArray PropertyNamesAndPointers; bool bHasAllEditorOnlyDataProperties = true; { FString SourceStruct; if (FUnrealFunctionDefinitionInfo* FunctionDef = UHTCast(StructDef)) { while (FunctionDef->GetSuperFunction()) { FunctionDef = FunctionDef->GetSuperFunction(); } FString FunctionName = FunctionDef->GetName(); if (FunctionDef->HasAnyFunctionFlags(FUNC_Delegate)) { FunctionName.LeftChopInline(HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX_LENGTH, false); } SourceStruct = GetEventStructParamsName(*FunctionDef->GetOuter(), *FunctionName); } else { SourceStruct = StructDef.GetAlternateNameCPP(); } FMacroBlockEmitter WithEditorOnlyMacroEmitter(Out, TEXT("WITH_EDITORONLY_DATA")); FMacroBlockEmitter WithEditorOnlyMacroEmitterDecl(DeclOut, TEXT("WITH_EDITORONLY_DATA")); for (TSharedRef PropertyDef : StructDef.GetProperties()) { bool bRequiresHasEditorOnlyMacro = IsEditorOnlyDataProperty(*PropertyDef); if (!bRequiresHasEditorOnlyMacro) { bHasAllEditorOnlyDataProperties = false; } WithEditorOnlyMacroEmitter(bRequiresHasEditorOnlyMacro); WithEditorOnlyMacroEmitterDecl(bRequiresHasEditorOnlyMacro); FString PropName = PropertyDef->GetName(); FString PropVariableName = FString::Printf(TEXT("%sNewProp_%s"), Scope, *PropName); if (PropertyDef->HasAllPropertyFlags(CPF_Deprecated)) { PropName += TEXT("_DEPRECATED"); } FString PropMacroOuterClass = FString::Printf(TEXT("STRUCT_OFFSET(%s, %s)"), *SourceStruct, *PropName); OutputProperty(DeclOut, Out, OutReferenceGatherers, Scope, PropertyNamesAndPointers, *PropertyDef, *PropMacroOuterClass, MoveTemp(PropVariableName), DeclSpaces, Spaces, *SourceStruct); } WithEditorOnlyMacroEmitter(bHasAllEditorOnlyDataProperties); WithEditorOnlyMacroEmitterDecl(bHasAllEditorOnlyDataProperties); DeclOut.Logf(TEXT("%sstatic const UECodeGen_Private::FPropertyParamsBase* const PropPointers[];\r\n"), DeclSpaces); Out.Logf(TEXT("%sconst UECodeGen_Private::FPropertyParamsBase* const %sPropPointers[] = {\r\n"), Spaces, Scope); for (const FPropertyNamePointerPair& PropNameAndPtr : PropertyNamesAndPointers) { bool bRequiresHasEditorOnlyMacro = IsEditorOnlyDataProperty(*PropNameAndPtr.PropDef); WithEditorOnlyMacroEmitter(bRequiresHasEditorOnlyMacro); WithEditorOnlyMacroEmitterDecl(bRequiresHasEditorOnlyMacro); Out.Logf(TEXT("%s\t(const UECodeGen_Private::FPropertyParamsBase*)&%s,\r\n"), Spaces, *PropNameAndPtr.Name); } WithEditorOnlyMacroEmitter(bHasAllEditorOnlyDataProperties); WithEditorOnlyMacroEmitterDecl(bHasAllEditorOnlyDataProperties); Out.Logf(TEXT("%s};\r\n"), Spaces); } if (bHasAllEditorOnlyDataProperties) { return TTuple( FString::Printf(TEXT("IF_WITH_EDITORONLY_DATA(%sPropPointers, nullptr)"), Scope), FString::Printf(TEXT("IF_WITH_EDITORONLY_DATA(UE_ARRAY_COUNT(%sPropPointers), 0)"), Scope) ); } else { return TTuple( FString::Printf(TEXT("%sPropPointers"), Scope), FString::Printf(TEXT("UE_ARRAY_COUNT(%sPropPointers)"), Scope) ); } } static bool IsAlwaysAccessible(FUnrealScriptStructDefinitionInfo& ScriptDef) { FName ToTest = ScriptDef.GetFName(); if (ToTest == NAME_Matrix || ToTest == NAME_Matrix44f || ToTest == NAME_Matrix44d) { return false; // special case, the C++ FMatrix does not have the same members. } bool Result = ScriptDef.HasDefaults(); // if we have cpp struct ops in it for UHT, then we can assume it is always accessible // LWC_TODO: //UScriptStruct::ICppStructOps* StructOps = UScriptStruct::FindDeferredCppStructOps(ToTest); //check(StructOps || !ToTest_falls_within_UnrealNames.inl_SpecialTypes); //return StructOps != nullptr; if( ToTest == NAME_Plane || ToTest == NAME_Plane4f || ToTest == NAME_Plane4d || ToTest == NAME_Vector || ToTest == NAME_Vector3f || ToTest == NAME_Vector3d || ToTest == NAME_Vector4 || ToTest == NAME_Vector4f || ToTest == NAME_Vector4d || ToTest == NAME_Box || ToTest == NAME_Box3f || ToTest == NAME_Box3d || ToTest == NAME_Quat || ToTest == NAME_Quat4f || ToTest == NAME_Quat4d || ToTest == NAME_Rotator || ToTest == NAME_Rotator3f || ToTest == NAME_Rotator3d || ToTest == NAME_Color ) { check(Result); } return Result; } static void FindNoExportStructsRecursive(TArray& StructDefs, FUnrealStructDefinitionInfo* StartDef) { for (; StartDef; StartDef = StartDef->GetSuperStructInfo().Struct) { if (FUnrealScriptStructDefinitionInfo* StartScriptDef = UHTCast(StartDef)) { if (StartScriptDef->HasAnyStructFlags(STRUCT_Native)) { break; } if (!IsAlwaysAccessible(*StartScriptDef)) // these are a special cases that already exists and if wrong if exported naively { // this will topologically sort them in reverse order StructDefs.Remove(&StartDef->AsScriptStructChecked()); StructDefs.Add(&StartDef->AsScriptStructChecked()); } } for (TSharedRef PropertyDef : StartDef->GetProperties()) { const FPropertyBase& PropertyBase = PropertyDef->GetPropertyBase(); if (UHTCast(PropertyBase.TypeDef) != nullptr) { FindNoExportStructsRecursive(StructDefs, PropertyBase.ScriptStructDef); } if (PropertyBase.MapKeyProp != nullptr && UHTCast(PropertyBase.MapKeyProp->TypeDef) != nullptr) { FindNoExportStructsRecursive(StructDefs, PropertyBase.MapKeyProp->ScriptStructDef); } } } } static TArray FindNoExportStructs(FUnrealStructDefinitionInfo* StartDef) { TArray Result; FindNoExportStructsRecursive(Result, StartDef); // These come out in reverse order of topology so reverse them Algo::Reverse(Result); return Result; } bool IsDelegateFunction(FUnrealFieldDefinitionInfo& FieldDef) { FUnrealFunctionDefinitionInfo* FunctionDef = UHTCast(FieldDef); return FunctionDef != nullptr && FunctionDef->IsDelegateFunction(); } void FNativeClassHeaderGenerator::ExportGeneratedPackageInitCode(FOutputDevice& Out, const TCHAR* InDeclarations, uint32 Hash) { UPackage* Package = PackageDef.GetPackage(); const FString& SingletonName = GetPackageSingletonNameFuncAddr(PackageDef, nullptr); FString PackageName = Package->GetName(); PackageName.ReplaceInline(TEXT("/"), TEXT("_"), ESearchCase::CaseSensitive); FString BodyHashFn = FString::Printf(TEXT("Z_UPackage_%s_BodyHash"), *PackageName); FString DeclarationsHashFn = FString::Printf(TEXT("Z_UPackage_%s_DeclarationsHash"), *PackageName); uint32 DeclarationsHash = GenerateTextHash(InDeclarations); TArray Singletons; for (TSharedRef& SourceFile : PackageDef.GetAllSourceFiles()) { Singletons.Append(SourceFile->GetSingletons()); } Algo::Sort(Singletons, [](FUnrealFieldDefinitionInfo* A, FUnrealFieldDefinitionInfo* B) { bool bADel = IsDelegateFunction(*A); bool bBDel = IsDelegateFunction(*B); if (bADel != bBDel) { return !bADel; } const FString& AName = A->GetSingletonName(true); const FString& BName = B->GetSingletonName(true); return AName < BName; }); #if UHT_ENABLE_EXTRA_HASH_OUTPUT Out.Log(TEXT("#if 0\r\n")); Out.Log(InDeclarations); Out.Log(TEXT("#endif\r\n")); #endif for (FUnrealFieldDefinitionInfo* FieldDef : Singletons) { Out.Log(FieldDef->GetExternDecl(true)); } FOutputDeviceNull OutputDeviceNull; FString MetaDataParams = OutputMetaDataCodeForObject(OutputDeviceNull, Out, PackageDef, TEXT("Package_MetaDataParams"), TEXT(""), TEXT("\t\t\t")); Out.Logf(TEXT("\tstatic FPackageRegistrationInfo Z_Registration_Info_UPackage_%s;\r\n"), *PackageName); Out.Logf(TEXT("\tFORCENOINLINE UPackage* %s()\r\n"), *SingletonName); Out.Logf(TEXT("\t{\r\n")); Out.Logf(TEXT("\t\tif (!Z_Registration_Info_UPackage_%s.OuterSingleton)\r\n"), *PackageName); Out.Logf(TEXT("\t\t{\r\n")); const TCHAR* SingletonArray; const TCHAR* SingletonCount; if (!Singletons.IsEmpty()) { Out.Logf(TEXT("\t\t\tstatic UObject* (*const SingletonFuncArray[])() = {\r\n")); for (FUnrealFieldDefinitionInfo* FieldDef : Singletons) { const FString& Name = FieldDef->GetSingletonNameChopped(true); Out.Logf(TEXT("\t\t\t\t(UObject* (*)())%s,\r\n"), *Name); } Out.Logf(TEXT("\t\t\t};\r\n")); SingletonArray = TEXT("SingletonFuncArray"); SingletonCount = TEXT("UE_ARRAY_COUNT(SingletonFuncArray)"); } else { SingletonArray = TEXT("nullptr"); SingletonCount = TEXT("0"); } Out.Logf(TEXT("\t\t\tstatic const UECodeGen_Private::FPackageParams PackageParams = {\r\n")); Out.Logf(TEXT("\t\t\t\t%s,\r\n"), *CreateUTF8LiteralString(Package->GetName())); Out.Logf(TEXT("\t\t\t\t%s,\r\n"), SingletonArray); Out.Logf(TEXT("\t\t\t\t%s,\r\n"), SingletonCount); Out.Logf(TEXT("\t\t\t\tPKG_CompiledIn | 0x%08X,\r\n"), Package->GetPackageFlags() & (PKG_ClientOptional | PKG_ServerSideOnly | PKG_EditorOnly | PKG_Developer | PKG_UncookedOnly)); Out.Logf(TEXT("\t\t\t\t0x%08X,\r\n"), Hash); Out.Logf(TEXT("\t\t\t\t0x%08X,\r\n"), DeclarationsHash); Out.Logf(TEXT("\t\t\t\t%s\r\n"), *MetaDataParams); Out.Logf(TEXT("\t\t\t};\r\n")); Out.Logf(TEXT("\t\t\tUECodeGen_Private::ConstructUPackage(Z_Registration_Info_UPackage_%s.OuterSingleton, PackageParams);\r\n"), *PackageName); Out.Logf(TEXT("\t\t}\r\n")); Out.Logf(TEXT("\t\treturn Z_Registration_Info_UPackage_%s.OuterSingleton;\r\n"), *PackageName); Out.Logf(TEXT("\t}\r\n")); // Do not change the Z_CompiledInDeferPackage_UPackage_ without changing LC_SymbolPatterns Out.Logf(TEXT("\tstatic FRegisterCompiledInInfo Z_CompiledInDeferPackage_UPackage_%s(%s, TEXT(\"%s\"), Z_Registration_Info_UPackage_%s, CONSTRUCT_RELOAD_VERSION_INFO(FPackageReloadVersionInfo, 0x%08X, 0x%08X));\r\n"), *PackageName, *SingletonName, *Package->GetName(), *PackageName, Hash, DeclarationsHash ); } void FNativeClassHeaderGenerator::ExportNativeGeneratedInitCode(FOutputDevice& Out, FOutputDevice& OutDeclarations, FReferenceGatherers& OutReferenceGatherers, const FUnrealSourceFile& SourceFile, FUnrealClassDefinitionInfo& ClassDef, FUHTStringBuilder& OutFriendText) const { check(!OutFriendText.Len()); const bool bIsNoExport = ClassDef.HasAnyClassFlags(CLASS_NoExport); const FString ClassNameCPP = ClassDef.GetAlternateNameCPP(); const FString& ApiString = GetAPIString(); TSet AlreadyIncludedNames; TArray FunctionsToExport; bool bAllEditorOnlyFunctions = true; for (TSharedRef LocalFuncDef : ClassDef.GetFunctions()) { FName TrueName = LocalFuncDef->GetFName(); bool bAlreadyIncluded = false; AlreadyIncludedNames.Add(TrueName, &bAlreadyIncluded); if (bAlreadyIncluded) { if (!LocalFuncDef->IsDelegateFunction()) { LocalFuncDef->Throwf(TEXT("The same function linked twice. Function: %s Class: %s"), *LocalFuncDef->GetName(), *ClassDef.GetName()); } continue; } if (!LocalFuncDef->IsDelegateFunction()) { bAllEditorOnlyFunctions &= LocalFuncDef->HasAnyFunctionFlags(FUNC_EditorOnly); } FunctionsToExport.Add(&*LocalFuncDef); } // Sort the list of functions Algo::SortBy(FunctionsToExport, [](FUnrealFunctionDefinitionInfo* Obj) { return Obj->GetName(); }); FUHTStringBuilder GeneratedClassRegisterFunctionText; // The class itself. { // simple ::StaticClass wrapper to avoid header, link and DLL hell { ClassDef.AddCrossModuleReference(OutReferenceGatherers.UniqueCrossModuleReferences, false); const FString& SingletonNameNoRegister = ClassDef.GetSingletonName(false); OutDeclarations.Log(ClassDef.GetExternDecl(false)); GeneratedClassRegisterFunctionText.Logf(TEXT("\tUClass* %s\r\n"), *SingletonNameNoRegister); GeneratedClassRegisterFunctionText.Logf(TEXT("\t{\r\n")); GeneratedClassRegisterFunctionText.Logf(TEXT("\t\treturn %s::StaticClass();\r\n"), *ClassNameCPP); GeneratedClassRegisterFunctionText.Logf(TEXT("\t}\r\n")); } // NOTE: We are adding the cross module reference here to avoid output differences. ClassDef.AddCrossModuleReference(OutReferenceGatherers.UniqueCrossModuleReferences, true); const FString& SingletonName = ClassDef.GetSingletonName(true); FString StaticsStructName = ClassDef.GetSingletonNameChopped(true) + TEXT("_Statics"); OutFriendText.Logf(TEXT("\tfriend struct %s;\r\n"), *StaticsStructName); OutDeclarations.Log(ClassDef.GetExternDecl(true)); GeneratedClassRegisterFunctionText.Logf(TEXT("\tstruct %s\r\n"), *StaticsStructName); GeneratedClassRegisterFunctionText.Logf(TEXT("\t{\r\n")); FUHTStringBuilder StaticDefinitions; FUHTStringBuilder Singletons; FUnrealClassDefinitionInfo* SuperClassDef = ClassDef.GetSuperClass(); if (SuperClassDef && SuperClassDef != &ClassDef) { SuperClassDef->AddCrossModuleReference(OutReferenceGatherers.UniqueCrossModuleReferences, true); OutDeclarations.Log(SuperClassDef->GetExternDecl(true)); Singletons.Logf(TEXT("\t\t(UObject* (*)())%s,\r\n"), *SuperClassDef->GetSingletonNameChopped(true)); } check(ClassDef.HasSource()); FUnrealPackageDefinitionInfo& ClassPackageDef = ClassDef.GetPackageDef(); PackageDef.AddCrossModuleReference(OutReferenceGatherers.UniqueCrossModuleReferences); OutDeclarations.Logf(TEXT("\t%s_API UPackage* %s;\r\n"), *ApiString, *ClassPackageDef.GetSingletonName()); Singletons.Logf(TEXT("\t\t(UObject* (*)())%s,\r\n"), *ClassPackageDef.GetSingletonNameChopped()); const TCHAR* SingletonsArray; const TCHAR* SingletonsCount; if (Singletons.Len() != 0) { GeneratedClassRegisterFunctionText.Logf(TEXT("\t\tstatic UObject* (*const DependentSingletons[])();\r\n")); StaticDefinitions.Logf(TEXT("\tUObject* (*const %s::DependentSingletons[])() = {\r\n"), *StaticsStructName); StaticDefinitions.Log (*Singletons); StaticDefinitions.Logf(TEXT("\t};\r\n")); SingletonsArray = TEXT("DependentSingletons"); SingletonsCount = TEXT("UE_ARRAY_COUNT(DependentSingletons)"); } else { SingletonsArray = TEXT("nullptr"); SingletonsCount = TEXT("0"); } const TCHAR* FunctionsArray; const TCHAR* FunctionsCount; if (FunctionsToExport.Num() != 0) { GeneratedClassRegisterFunctionText.Log(BEGIN_WRAP_EDITOR_ONLY(bAllEditorOnlyFunctions)); GeneratedClassRegisterFunctionText.Log(TEXT("\t\tstatic const FClassFunctionLinkInfo FuncInfo[];\r\n")); GeneratedClassRegisterFunctionText.Log(END_WRAP_EDITOR_ONLY(bAllEditorOnlyFunctions)); StaticDefinitions.Log(BEGIN_WRAP_EDITOR_ONLY(bAllEditorOnlyFunctions)); StaticDefinitions.Logf(TEXT("\tconst FClassFunctionLinkInfo %s::FuncInfo[] = {\r\n"), *StaticsStructName); for (FUnrealFunctionDefinitionInfo* FunctionDef : FunctionsToExport) { const bool bIsEditorOnlyFunction = FunctionDef->HasAnyFunctionFlags(FUNC_EditorOnly); if (!FunctionDef->IsDelegateFunction()) { ExportFunction(Out, OutReferenceGatherers, SourceFile, *FunctionDef, bIsNoExport); } FUHTStringBuilder FuncHashTag; FunctionDef->GetHashTag(ClassDef, FuncHashTag); StaticDefinitions.Logf( TEXT("%s\t\t{ &%s, %s },%s\r\n%s"), BEGIN_WRAP_EDITOR_ONLY(bIsEditorOnlyFunction), *GetSingletonNameFuncAddr(FunctionDef, OutReferenceGatherers.UniqueCrossModuleReferences), *CreateUTF8LiteralString(FunctionDef->GetName()), *FuncHashTag, END_WRAP_EDITOR_ONLY(bIsEditorOnlyFunction) ); } StaticDefinitions.Log(TEXT("\t};\r\n")); StaticDefinitions.Log(END_WRAP_EDITOR_ONLY(bAllEditorOnlyFunctions)); if (bAllEditorOnlyFunctions) { FunctionsArray = TEXT("IF_WITH_EDITOR(FuncInfo, nullptr)"); FunctionsCount = TEXT("IF_WITH_EDITOR(UE_ARRAY_COUNT(FuncInfo), 0)"); } else { FunctionsArray = TEXT("FuncInfo"); FunctionsCount = TEXT("UE_ARRAY_COUNT(FuncInfo)"); } } else { FunctionsArray = TEXT("nullptr"); FunctionsCount = TEXT("0"); } if (ClassDef.IsObjectInitializerConstructorDeclared()) { ClassDef.SetMetaData(NAME_ObjectInitializerConstructorDeclared, TEXT("")); } FString MetaDataParams = OutputMetaDataCodeForObject(GeneratedClassRegisterFunctionText, StaticDefinitions, ClassDef, *FString::Printf(TEXT("%s::Class_MetaDataParams"), *StaticsStructName), TEXT("\t\t"), TEXT("\t")); TTuple PropertyRange = OutputProperties(GeneratedClassRegisterFunctionText, StaticDefinitions, OutReferenceGatherers, *FString::Printf(TEXT("%s::"), *StaticsStructName), ClassDef, TEXT("\t\t"), TEXT("\t")); const TCHAR* InterfaceArray; const TCHAR* InterfaceCount; // Check to see if we have any interfaces bool bHasInterfaces = false; for (FUnrealStructDefinitionInfo::FBaseStructInfo& BaseStruct : ClassDef.GetBaseStructInfos()) { if (FUnrealClassDefinitionInfo* BaseClass = UHTCast(BaseStruct.Struct); BaseClass != nullptr && BaseClass->IsInterface()) { bHasInterfaces = true; break; } } if (bHasInterfaces) { GeneratedClassRegisterFunctionText.Log(TEXT("\t\tstatic const UECodeGen_Private::FImplementedInterfaceParams InterfaceParams[];\r\n")); StaticDefinitions.Logf(TEXT("\t\tconst UECodeGen_Private::FImplementedInterfaceParams %s::InterfaceParams[] = {\r\n"), *StaticsStructName); for (FUnrealStructDefinitionInfo::FBaseStructInfo& BaseStruct : ClassDef.GetBaseStructInfos()) { if (FUnrealClassDefinitionInfo* BaseClass = UHTCast(BaseStruct.Struct); BaseClass == nullptr || !BaseClass->IsInterface()) { continue; } FUnrealClassDefinitionInfo& InterClassDef = UHTCastChecked(BaseStruct.Struct); InterClassDef.AddCrossModuleReference(OutReferenceGatherers.UniqueCrossModuleReferences, false); FString OffsetString = FString::Printf(TEXT("(int32)VTABLE_OFFSET(%s, %s)"), *ClassNameCPP, *InterClassDef.GetAlternateNameCPP(true)); FUHTStringBuilder IntHash; InterClassDef.GetHashTag(ClassDef, IntHash); StaticDefinitions.Logf( TEXT("\t\t\t{ %s, %s, %s }, %s\r\n"), *InterClassDef.GetSingletonNameChopped(false), *OffsetString, TEXT("false"), *IntHash ); } StaticDefinitions.Log(TEXT("\t\t};\r\n")); InterfaceArray = TEXT("InterfaceParams"); InterfaceCount = TEXT("UE_ARRAY_COUNT(InterfaceParams)"); } else { InterfaceArray = TEXT("nullptr"); InterfaceCount = TEXT("0"); } GeneratedClassRegisterFunctionText.Logf(TEXT("\t\tstatic const FCppClassTypeInfoStatic StaticCppClassTypeInfo;\r\n")); StaticDefinitions.Logf(TEXT("\tconst FCppClassTypeInfoStatic %s::StaticCppClassTypeInfo = {\r\n"), *StaticsStructName); StaticDefinitions.Logf(TEXT("\t\tTCppClassTypeTraits<%s>::IsAbstract,\r\n"), *ClassDef.GetAlternateNameCPP(ClassDef.HasAllClassFlags(CLASS_Interface))); StaticDefinitions.Logf(TEXT("\t};\r\n")); GeneratedClassRegisterFunctionText.Log (TEXT("\t\tstatic const UECodeGen_Private::FClassParams ClassParams;\r\n")); uint32 ClassFlags = (uint32)ClassDef.GetClassFlags(); if (!bIsNoExport) { ClassFlags = ClassFlags | CLASS_MatchedSerializers; } ClassFlags = ClassFlags & CLASS_SaveInCompiledInClasses; StaticDefinitions.Logf(TEXT("\tconst UECodeGen_Private::FClassParams %s::ClassParams = {\r\n"), *StaticsStructName); StaticDefinitions.Logf(TEXT("\t\t&%s::StaticClass,\r\n"), *ClassNameCPP); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), (ClassDef.GetClassConfigName() != NAME_None) ? *CreateUTF8LiteralString(ClassDef.GetClassConfigName().ToString()) : TEXT("nullptr")); StaticDefinitions.Log (TEXT("\t\t&StaticCppClassTypeInfo,\r\n")); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), SingletonsArray); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), FunctionsArray); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), *PropertyRange.Get<0>()); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), InterfaceArray); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), SingletonsCount); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), FunctionsCount); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), *PropertyRange.Get<1>()); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), InterfaceCount); StaticDefinitions.Logf(TEXT("\t\t0x%08Xu,\r\n"), ClassFlags); StaticDefinitions.Logf(TEXT("\t\t%s\r\n"), *MetaDataParams); StaticDefinitions.Log (TEXT("\t};\r\n")); GeneratedClassRegisterFunctionText.Logf(TEXT("\t};\r\n")); GeneratedClassRegisterFunctionText.Log(*StaticDefinitions); Out.Logf(TEXT("\tIMPLEMENT_CLASS_NO_AUTO_REGISTRATION(%s);\r\n"), *ClassNameCPP); GeneratedClassRegisterFunctionText.Logf(TEXT("\tUClass* %s\r\n"), *SingletonName); GeneratedClassRegisterFunctionText.Logf(TEXT("\t{\r\n")); FString OuterSingletonName = FString::Printf(TEXT("Z_Registration_Info_UClass_%s.OuterSingleton"), *ClassNameCPP); GeneratedClassRegisterFunctionText.Logf(TEXT("\t\tif (!%s)\r\n"), *OuterSingletonName); GeneratedClassRegisterFunctionText.Logf(TEXT("\t\t{\r\n")); GeneratedClassRegisterFunctionText.Logf(TEXT("\t\t\tUECodeGen_Private::ConstructUClass(%s, %s::ClassParams);\r\n"), *OuterSingletonName, *StaticsStructName); TArray SparseClassDataTypes; ClassDef.GetSparseClassDataTypes(SparseClassDataTypes); for (const FString& SparseClassDataString : SparseClassDataTypes) { GeneratedClassRegisterFunctionText.Logf(TEXT("\t\t\t%s->SetSparseClassDataStruct(F%s::StaticStruct());\r\n"), *OuterSingletonName, *SparseClassDataString); } GeneratedClassRegisterFunctionText.Logf(TEXT("\t\t}\r\n")); GeneratedClassRegisterFunctionText.Logf(TEXT("\t\treturn %s;\r\n"), *OuterSingletonName); GeneratedClassRegisterFunctionText.Logf(TEXT("\t}\r\n")); Out.Log(*GeneratedClassRegisterFunctionText); } if (OutFriendText.Len() && bIsNoExport) { Out.Logf(TEXT("\t/* friend declarations for pasting into noexport class %s\r\n"), *ClassNameCPP); Out.Log(OutFriendText); Out.Logf(TEXT("\t*/\r\n")); OutFriendText.Reset(); } FString SingletonName = ClassDef.GetSingletonName(true); SingletonName.ReplaceInline(TEXT("()"), TEXT(""), ESearchCase::CaseSensitive); // function address // Append base class' hash at the end of the generated code, this will force update derived classes // when base class changes during hot-reload. uint32 BaseClassHash = 0; FUnrealClassDefinitionInfo* SuperClassDef = ClassDef.GetSuperClass(); if (SuperClassDef && !SuperClassDef->HasAnyClassFlags(CLASS_Intrinsic)) { BaseClassHash = SuperClassDef->GetHash(ClassDef); } FUHTStringBuilder HashBuilder; HashBuilder.Logf(TEXT("\r\n// %u\r\n"), BaseClassHash); // Append info for the sparse class data struct onto the text to be hashed TArray SparseClassDataTypes; ClassDef.GetSparseClassDataTypes(SparseClassDataTypes); for (const FString& SparseClassDataString : SparseClassDataTypes) { if (FUnrealScriptStructDefinitionInfo* SparseScriptStructDef = GTypeDefinitionInfoMap.FindByName(*SparseClassDataString)) { HashBuilder.Logf(TEXT("%s\r\n"), *SparseScriptStructDef->GetName()); for (FUnrealPropertyDefinitionInfo* ChildDef : TUHTFieldRange(*SparseScriptStructDef)) { HashBuilder.Logf(TEXT("%s %s\r\n"), *ChildDef->GetCPPType(), *ChildDef->GetNameWithDeprecated()); } } } #if UHT_ENABLE_EXTRA_HASH_OUTPUT Out.Log(TEXT("#if 0\r\n")); Out.Log(HashBuilder); Out.Log(TEXT("#endif\r\n")); #endif GeneratedClassRegisterFunctionText.Log(HashBuilder); // Calculate generated class initialization code hash so that we know when it changes after hot-reload uint32 ClassHash = GenerateTextHash(*GeneratedClassRegisterFunctionText); ClassDef.SetHash(ClassHash); Out.Logf(TEXT("\ttemplate<> %sUClass* StaticClass<%s>()\r\n"), *GetAPIString(), *ClassNameCPP); Out.Logf(TEXT("\t{\r\n")); Out.Logf(TEXT("\t\treturn %s::StaticClass();\r\n"), *ClassNameCPP); Out.Logf(TEXT("\t}\r\n")); if (ClassHasReplicatedProperties(ClassDef)) { Out.Logf(TEXT( "\r\n" "\tvoid %s::ValidateGeneratedRepEnums(const TArray& ClassReps) const\r\n" "\t{\r\n" ), *ClassNameCPP); FUHTStringBuilder NameBuilder; FUHTStringBuilder ValidationBuilder; ValidationBuilder.Log(TEXT("\t\tconst bool bIsValid = true")); for (int32 i = ClassDef.GetFirstOwnedClassRep(); i < ClassDef.GetClassReps().Num(); ++i) { const FUnrealPropertyDefinitionInfo* PropertyDef = ClassDef.GetClassReps()[i]; const FString PropertyName = PropertyDef->GetName(); NameBuilder.Logf(TEXT("\t\tstatic const FName Name_%s(TEXT(\"%s\"));\r\n"), *PropertyName, *PropertyName); if (!PropertyDef->IsStaticArray()) { ValidationBuilder.Logf(TEXT("\r\n\t\t\t&& Name_%s == ClassReps[(int32)ENetFields_Private::%s].Property->GetFName()"), *PropertyName, *PropertyName); } else { ValidationBuilder.Logf(TEXT("\r\n\t\t\t&& Name_%s == ClassReps[(int32)ENetFields_Private::%s_STATIC_ARRAY].Property->GetFName()"), *PropertyName, *PropertyName); } } ValidationBuilder.Log(TEXT(";\r\n")); Out.Logf(TEXT( "%s\r\n" // NameBuilder "%s\r\n" // ValidationBuilder "\t\tcheckf(bIsValid, TEXT(\"UHT Generated Rep Indices do not match runtime populated Rep Indices for properties in %s\"));\r\n" "\t}\r\n" ), *NameBuilder, *ValidationBuilder, *ClassNameCPP); } } void FNativeClassHeaderGenerator::ExportFunction(FOutputDevice& Out, FReferenceGatherers& OutReferenceGatherers, const FUnrealSourceFile& SourceFile, FUnrealFunctionDefinitionInfo& FunctionDef, bool bIsNoExport) const { FunctionDef.AddCrossModuleReference(OutReferenceGatherers.UniqueCrossModuleReferences, true); FUnrealFunctionDefinitionInfo* SuperFunctionDef = FunctionDef.GetSuperFunction(); const bool bIsEditorOnlyFunction = FunctionDef.HasAnyFunctionFlags(FUNC_EditorOnly); bool bIsDelegate = FunctionDef.HasAnyFunctionFlags(FUNC_Delegate); const FString& SingletonName = FunctionDef.GetSingletonName(true); FString StaticsStructName = FunctionDef.GetSingletonNameChopped(true) + TEXT("_Statics"); FUHTStringBuilder CurrentFunctionText; FUHTStringBuilder StaticDefinitions; // Begin wrapping editor only functions. Note: This should always be the first step! if (bIsEditorOnlyFunction) { CurrentFunctionText.Logf(BeginEditorOnlyGuard); } CurrentFunctionText.Logf(TEXT("\tstruct %s\r\n"), *StaticsStructName); CurrentFunctionText.Log (TEXT("\t{\r\n")); bool bParamsInStatic = bIsNoExport || !FunctionDef.HasAnyFunctionFlags(FUNC_Event); // non-events do not export a params struct, so lets do that locally for offset determination if (bParamsInStatic) { TArray StructDefs = FindNoExportStructs(&FunctionDef); for (FUnrealScriptStructDefinitionInfo* StructDef : StructDefs) { ExportMirrorsForNoexportStruct(CurrentFunctionText, *StructDef, /*Indent=*/ 2); } ExportEventParm(CurrentFunctionText, OutReferenceGatherers.ForwardDeclarations, FunctionDef, /*Indent=*/ 2, /*bOutputConstructor=*/ false, EExportingState::TypeEraseDelegates); } FUnrealFieldDefinitionInfo* FieldOuterDef = UHTCast(FunctionDef.GetOuter()); FString OuterFunc; if (FUnrealObjectDefinitionInfo* OuterDef = FunctionDef.GetOuter()) { FUnrealPackageDefinitionInfo* OuterPackageDef = UHTCast(OuterDef); OuterFunc = OuterPackageDef ? GetPackageSingletonNameFuncAddr(*OuterPackageDef, OutReferenceGatherers.UniqueCrossModuleReferences) : GetSingletonNameFuncAddr(FunctionDef.GetOwnerClass(), OutReferenceGatherers.UniqueCrossModuleReferences); } else { OuterFunc = TEXT("nullptr"); } FString StructureSize; if (FunctionDef.GetProperties().Num()) { FUnrealFunctionDefinitionInfo* TempFunctionDef = &FunctionDef; while (TempFunctionDef->GetSuperFunction()) { TempFunctionDef = TempFunctionDef->GetSuperFunction(); } FString FunctionName = TempFunctionDef->GetName(); if (TempFunctionDef->HasAnyFunctionFlags(FUNC_Delegate)) { FunctionName.LeftChopInline(HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX_LENGTH, false); } if (bParamsInStatic) { StructureSize = FString::Printf(TEXT("sizeof(%s::%s)"), *StaticsStructName, *GetEventStructParamsName(*TempFunctionDef->GetOuter(), *FunctionName)); } else { StructureSize = FString::Printf(TEXT("sizeof(%s)"), *GetEventStructParamsName(*TempFunctionDef->GetOuter(), *FunctionName)); } } else { StructureSize = TEXT("0"); } bool bIsSparse = FunctionDef.GetFunctionType() == EFunctionType::SparseDelegate; const TCHAR* UFunctionObjectFlags = TEXT("RF_Public|RF_Transient|RF_MarkAsNative"); TTuple PropertyRange = OutputProperties(CurrentFunctionText, StaticDefinitions, OutReferenceGatherers, *FString::Printf(TEXT("%s::"), *StaticsStructName), FunctionDef, TEXT("\t\t"), TEXT("\t")); const FFuncInfo& FunctionData = FunctionDef.GetFunctionData(); const bool bIsNet = FunctionDef.HasAnyFunctionFlags(FUNC_NetRequest | FUNC_NetResponse); FString MetaDataParams = OutputMetaDataCodeForObject(CurrentFunctionText, StaticDefinitions, FunctionDef, *FString::Printf(TEXT("%s::Function_MetaDataParams"), *StaticsStructName), TEXT("\t\t"), TEXT("\t")); CurrentFunctionText.Log(TEXT("\t\tstatic const UECodeGen_Private::FFunctionParams FuncParams;\r\n")); StaticDefinitions.Logf( TEXT("\tconst UECodeGen_Private::FFunctionParams %s::FuncParams = { (UObject*(*)())%s, %s, %s, %s, %s, %s, %s, %s, %s, (EFunctionFlags)0x%08X, %d, %d, %s };\r\n"), *StaticsStructName, *OuterFunc, *GetSingletonNameFuncAddr(SuperFunctionDef, OutReferenceGatherers.UniqueCrossModuleReferences), *CreateUTF8LiteralString(FunctionDef.GetName()), (bIsSparse ? *CreateUTF8LiteralString(FunctionDef.GetSparseOwningClassName().ToString()) : TEXT("nullptr")), (bIsSparse ? *CreateUTF8LiteralString(FunctionDef.GetSparseDelegateName().ToString()) : TEXT("nullptr")), *StructureSize, *PropertyRange.Get<0>(), *PropertyRange.Get<1>(), UFunctionObjectFlags, (uint32)FunctionDef.GetFunctionFlags(), bIsNet ? FunctionData.RPCId : 0, bIsNet ? FunctionData.RPCResponseId : 0, *MetaDataParams ); CurrentFunctionText.Log(TEXT("\t};\r\n")); CurrentFunctionText.Log(*StaticDefinitions); CurrentFunctionText.Logf(TEXT("\tUFunction* %s\r\n"), *SingletonName); CurrentFunctionText.Log (TEXT("\t{\r\n")); CurrentFunctionText.Logf(TEXT("\t\tstatic UFunction* ReturnFunction = nullptr;\r\n")); CurrentFunctionText.Logf(TEXT("\t\tif (!ReturnFunction)\r\n")); CurrentFunctionText.Logf(TEXT("\t\t{\r\n")); CurrentFunctionText.Logf(TEXT("\t\t\tUECodeGen_Private::ConstructUFunction(&ReturnFunction, %s::FuncParams);\r\n"), *StaticsStructName); CurrentFunctionText.Log (TEXT("\t\t}\r\n")); CurrentFunctionText.Log (TEXT("\t\treturn ReturnFunction;\r\n")); CurrentFunctionText.Log (TEXT("\t}\r\n")); // End wrapping editor only functions. Note: This should always be the last step! if (bIsEditorOnlyFunction) { CurrentFunctionText.Logf(EndEditorOnlyGuard); } uint32 FunctionHash = GenerateTextHash(*CurrentFunctionText); FunctionDef.SetHash(FunctionHash); Out.Log(CurrentFunctionText); } void FNativeClassHeaderGenerator::ExportNatives(FOutputDevice& Out, FUnrealClassDefinitionInfo& ClassDef) { const FString ClassCPPName = ClassDef.GetAlternateNameCPP(); const FString TypeName = ClassDef.GetAlternateNameCPP(ClassDef.HasAnyClassFlags(CLASS_Interface)); Out.Logf(TEXT("\tvoid %s::StaticRegisterNatives%s()\r\n"), *ClassCPPName, *ClassCPPName); Out.Log(TEXT("\t{\r\n")); { bool bAllEditorOnly = true; TArray> NamedFunctionsToExport; for (TSharedRef FunctionDef : ClassDef.GetFunctions()) { if (FunctionDef->HasSpecificFunctionFlags(FUNC_Native | FUNC_NetRequest, FUNC_Native)) { FString FunctionName = CreateUTF8LiteralString(FunctionDef->GetName()); NamedFunctionsToExport.Emplace(&*FunctionDef, MoveTemp(FunctionName)); if (!FunctionDef->HasAnyFunctionFlags(FUNC_EditorOnly)) { bAllEditorOnly = false; } } } Algo::SortBy(NamedFunctionsToExport, [](const TTuple& Pair){ return Pair.Get<0>()->GetFName(); }, FNameLexicalLess()); if (NamedFunctionsToExport.Num() > 0) { FMacroBlockEmitter EditorOnly(Out, TEXT("WITH_EDITOR")); EditorOnly(bAllEditorOnly); Out.Logf(TEXT("\t\tUClass* Class = %s::StaticClass();\r\n"), *ClassCPPName); Out.Log(TEXT("\t\tstatic const FNameNativePtrPair Funcs[] = {\r\n")); for (const TTuple& Func : NamedFunctionsToExport) { FUnrealFunctionDefinitionInfo* FunctionDef = Func.Get<0>(); EditorOnly(FunctionDef->HasAnyFunctionFlags(FUNC_EditorOnly)); Out.Logf( TEXT("\t\t\t{ %s, &%s::exec%s },\r\n"), *Func.Get<1>(), *TypeName, *FunctionDef->GetName() ); } EditorOnly(bAllEditorOnly); Out.Log(TEXT("\t\t};\r\n")); Out.Logf(TEXT("\t\tFNativeFunctionRegistrar::RegisterFunctions(Class, Funcs, UE_ARRAY_COUNT(Funcs));\r\n")); } } Out.Logf(TEXT("\t}\r\n")); } void FNativeClassHeaderGenerator::ExportInterfaceCallFunctions(FOutputDevice& OutCpp, FUHTStringBuilder& Out, FReferenceGatherers& OutReferenceGatherers, const TArray& CallbackFunctions, const TCHAR* ClassName) const { const FString& APIString = GetAPIString(); for (FUnrealFunctionDefinitionInfo* FunctionDef : CallbackFunctions) { FString FunctionName = FunctionDef->GetName(); const FFuncInfo& FunctionData = FunctionDef->GetFunctionData(); const TCHAR* ConstQualifier = FunctionDef->HasAllFunctionFlags(FUNC_Const) ? TEXT("const ") : TEXT(""); FString ExtraParam = FString::Printf(TEXT("%sUObject* O"), ConstQualifier); ExportNativeFunctionHeader(Out, OutReferenceGatherers.ForwardDeclarations, *FunctionDef, FunctionData, EExportFunctionType::Interface, EExportFunctionHeaderStyle::Declaration, *ExtraParam, *APIString); Out.Logf( TEXT(";" LINE_TERMINATOR_ANSI) ); FString FunctionNameName = FString::Printf(TEXT("NAME_%s_%s"), *UHTCastChecked(FunctionDef->GetOuter()).GetAlternateNameCPP(), *FunctionName); OutCpp.Logf(TEXT("\tstatic FName %s = FName(TEXT(\"%s\"));" LINE_TERMINATOR_ANSI), *FunctionNameName, *FunctionDef->GetFName().ToString()); ExportNativeFunctionHeader(OutCpp, OutReferenceGatherers.ForwardDeclarations, *FunctionDef, FunctionData, EExportFunctionType::Interface, EExportFunctionHeaderStyle::Definition, *ExtraParam, *APIString); OutCpp.Logf( TEXT(LINE_TERMINATOR_ANSI "\t{" LINE_TERMINATOR_ANSI) ); OutCpp.Logf(TEXT("\t\tcheck(O != NULL);" LINE_TERMINATOR_ANSI)); OutCpp.Logf(TEXT("\t\tcheck(O->GetClass()->ImplementsInterface(U%s::StaticClass()));" LINE_TERMINATOR_ANSI), ClassName); FParmsAndReturnProperties Parameters = GetFunctionParmsAndReturn(*FunctionDef); // See if we need to create Parms struct const bool bHasParms = Parameters.HasParms(); if (bHasParms) { FString EventParmStructName = GetEventStructParamsName(*FunctionDef->GetOuter(), *FunctionName); OutCpp.Logf(TEXT("\t\t%s Parms;" LINE_TERMINATOR_ANSI), *EventParmStructName); } OutCpp.Logf(TEXT("\t\tUFunction* const Func = O->FindFunction(%s);" LINE_TERMINATOR_ANSI), *FunctionNameName); OutCpp.Log(TEXT("\t\tif (Func)" LINE_TERMINATOR_ANSI)); OutCpp.Log(TEXT("\t\t{" LINE_TERMINATOR_ANSI)); // code to populate Parms struct for (FUnrealPropertyDefinitionInfo* ParamDef : Parameters.Parms) { const FString ParamName = ParamDef->GetNameWithDeprecated(); OutCpp.Logf(TEXT("\t\t\tParms.%s=%s;" LINE_TERMINATOR_ANSI), *ParamName, *ParamName); } const FString ObjectRef = FunctionDef->HasAllFunctionFlags(FUNC_Const) ? FString::Printf(TEXT("const_cast(O)")) : TEXT("O"); OutCpp.Logf(TEXT("\t\t\t%s->ProcessEvent(Func, %s);" LINE_TERMINATOR_ANSI), *ObjectRef, bHasParms ? TEXT("&Parms") : TEXT("NULL")); for (FUnrealPropertyDefinitionInfo* ParamDef : Parameters.Parms) { if(ParamDef->HasAllPropertyFlags(CPF_OutParm) && !ParamDef->HasAnyPropertyFlags(CPF_ConstParm|CPF_ReturnParm)) { const FString ParamName = ParamDef->GetNameWithDeprecated(); OutCpp.Logf(TEXT("\t\t\t%s=Parms.%s;" LINE_TERMINATOR_ANSI), *ParamName, *ParamName); } } OutCpp.Log(TEXT("\t\t}" LINE_TERMINATOR_ANSI)); // else clause to call back into native if it's a BlueprintNativeEvent if (FunctionDef->HasAnyFunctionFlags(FUNC_Native)) { OutCpp.Logf(TEXT("\t\telse if (auto I = (%sI%s*)(O->GetNativeInterfaceAddress(U%s::StaticClass())))" LINE_TERMINATOR_ANSI), ConstQualifier, ClassName, ClassName); OutCpp.Log(TEXT("\t\t{" LINE_TERMINATOR_ANSI)); OutCpp.Log(TEXT("\t\t\t")); if (Parameters.Return) { OutCpp.Log(TEXT("Parms.ReturnValue = ")); } OutCpp.Logf(TEXT("I->%s_Implementation("), *FunctionName); bool bFirst = true; for (FUnrealPropertyDefinitionInfo* ParamDef : Parameters.Parms) { if (!bFirst) { OutCpp.Logf(TEXT(",")); } bFirst = false; OutCpp.Log(*ParamDef->GetName()); } OutCpp.Logf(TEXT(");" LINE_TERMINATOR_ANSI)); OutCpp.Logf(TEXT("\t\t}" LINE_TERMINATOR_ANSI)); } if (Parameters.Return) { OutCpp.Logf(TEXT("\t\treturn Parms.ReturnValue;" LINE_TERMINATOR_ANSI)); } OutCpp.Logf(TEXT("\t}" LINE_TERMINATOR_ANSI)); } } /** * Gets preprocessor string to emit GENERATED_U*_BODY() macro is deprecated. * * @param MacroName Name of the macro to be deprecated. * * @returns Preprocessor string to emit the message. */ FString GetGeneratedMacroDeprecationWarning(const TCHAR* MacroName) { // Deprecation warning is disabled right now. After people get familiar with the new macro it should be re-enabled. //return FString() + TEXT("EMIT_DEPRECATED_WARNING_MESSAGE(\"") + MacroName + TEXT("() macro is deprecated. Please use GENERATED_BODY() macro instead.\")") LINE_TERMINATOR; return TEXT(""); } /** * Returns a string with access specifier that was met before parsing GENERATED_BODY() macro to preserve it. * * @param Class Class for which to return the access specifier. * * @returns Access specifier string. */ FString GetPreservedAccessSpecifierString(FUnrealClassDefinitionInfo& ClassDef) { FString PreservedAccessSpecifier; switch (ClassDef.GetGeneratedBodyMacroAccessSpecifier()) { case EAccessSpecifier::ACCESS_Private: PreservedAccessSpecifier = "private:"; break; case EAccessSpecifier::ACCESS_Protected: PreservedAccessSpecifier = "protected:"; break; case EAccessSpecifier::ACCESS_Public: PreservedAccessSpecifier = "public:"; break; case EAccessSpecifier::ACCESS_NotAnAccessSpecifier : PreservedAccessSpecifier = FString::Printf(TEXT("static_assert(false, \"Unknown access specifier for GENERATED_BODY() macro in class %s.\");"), *ClassDef.GetName()); break; } return PreservedAccessSpecifier + LINE_TERMINATOR; } void WriteMacro(FOutputDevice& Output, const FString& MacroName, FString MacroContent) { Output.Log(Macroize(*MacroName, MoveTemp(MacroContent))); } void FNativeClassHeaderGenerator::ExportClassFromSourceFileInner( FOutputDevice& OutGeneratedHeaderText, FOutputDevice& OutCpp, FOutputDevice& OutDeclarations, FReferenceGatherers& OutReferenceGatherers, FUnrealClassDefinitionInfo& ClassDef, const FUnrealSourceFile& SourceFile, EExportClassOutFlags& OutFlags ) const { FUHTStringBuilder StandardUObjectConstructorsMacroCall; FUHTStringBuilder EnhancedUObjectConstructorsMacroCall; FUnrealClassDefinitionInfo* SuperClassDef = ClassDef.GetSuperClass(); // C++ -> VM stubs (native function execs) FUHTStringBuilder ClassMacroCalls; FUHTStringBuilder ClassNoPureDeclsMacroCalls; ExportNativeFunctions(OutGeneratedHeaderText, OutCpp, ClassMacroCalls, ClassNoPureDeclsMacroCalls, OutReferenceGatherers, SourceFile, ClassDef); // Get Callback functions TArray CallbackFunctions; for (TSharedRef FunctionDef : ClassDef.GetFunctions()) { if (FunctionDef->HasAnyFunctionFlags(FUNC_Event) && FunctionDef->GetSuperFunction() == nullptr) { CallbackFunctions.Add(&*FunctionDef); } } FUHTStringBuilder PrologMacroCalls; if (CallbackFunctions.Num() != 0) { Algo::SortBy(CallbackFunctions, [](FUnrealFunctionDefinitionInfo* Obj) { return Obj->GetName(); }); FUHTStringBuilder UClassMacroContent; // export parameters structs for all events and delegates for (FUnrealFunctionDefinitionInfo* FunctionDef : CallbackFunctions) { ExportEventParm(UClassMacroContent, OutReferenceGatherers.ForwardDeclarations, *FunctionDef, /*Indent=*/ 1, /*bOutputConstructor=*/ true, EExportingState::Normal); } FString MacroName = SourceFile.GetGeneratedMacroName(ClassDef.GetGeneratedBodyLine(), TEXT("_EVENT_PARMS")); WriteMacro(OutGeneratedHeaderText, MacroName, UClassMacroContent); PrologMacroCalls.Logf(TEXT("\t%s\r\n"), *MacroName); // VM -> C++ proxies (events and delegates). FOutputDeviceNull NullOutput; FOutputDevice& CallbackOut = ClassDef.HasAnyClassFlags(CLASS_NoExport) ? NullOutput : OutCpp; FString CallbackWrappersMacroName = SourceFile.GetGeneratedMacroName(ClassDef.GetGeneratedBodyLine(), TEXT("_CALLBACK_WRAPPERS")); ExportCallbackFunctions( OutGeneratedHeaderText, CallbackOut, OutReferenceGatherers.ForwardDeclarations, CallbackFunctions, *CallbackWrappersMacroName, ClassDef.HasAnyClassFlags(CLASS_Interface) ? EExportCallbackType::Interface : EExportCallbackType::Class, *GetAPIString() ); ClassMacroCalls.Logf(TEXT("\t%s\r\n"), *CallbackWrappersMacroName); ClassNoPureDeclsMacroCalls.Logf(TEXT("\t%s\r\n"), *CallbackWrappersMacroName); } // Class definition. if (!ClassDef.HasAnyClassFlags(CLASS_NoExport)) { ExportNatives(OutCpp, ClassDef); } FUHTStringBuilder FriendText; ExportNativeGeneratedInitCode(OutCpp, OutDeclarations, OutReferenceGatherers, SourceFile, ClassDef, FriendText); // the name for the C++ version of the UClass const FString ClassCPPName = ClassDef.GetAlternateNameCPP(); const FString SuperClassCPPName = (SuperClassDef ? SuperClassDef->GetAlternateNameCPP() : TEXT("None")); FString APIArg = PackageDef.GetShortUpperName(); if (!ClassDef.HasAnyClassFlags(CLASS_MinimalAPI)) { APIArg = TEXT("NO"); } FString GeneratedSerializeFunctionCPP; FString GeneratedSerializeFunctionHeaderMacroName; // Only write out adapters if the user has provided one or the other of the Serialize overloads if (FMath::CountBits((uint32)ClassDef.GetArchiveType()) == 1) { FUHTStringBuilder Boilerplate, BoilerPlateCPP; const TCHAR* MacroNameHeader; const TCHAR* MacroNameCPP; GeneratedSerializeFunctionHeaderMacroName = SourceFile.GetGeneratedMacroName(ClassDef.GetGeneratedBodyLine(), TEXT("_ARCHIVESERIALIZER")); if (ClassDef.GetArchiveType() == ESerializerArchiveType::StructuredArchiveRecord) { MacroNameHeader = TEXT("DECLARE_FARCHIVE_SERIALIZER"); MacroNameCPP = TEXT("IMPLEMENT_FARCHIVE_SERIALIZER"); } else { MacroNameHeader = TEXT("DECLARE_FSTRUCTUREDARCHIVE_SERIALIZER"); MacroNameCPP = TEXT("IMPLEMENT_FSTRUCTUREDARCHIVE_SERIALIZER"); } // if the existing Serialize function was wrapped in a compiler define directive, we need to replicate that on the generated function if (ClassDef.GetEnclosingDefine().Len()) { OutGeneratedHeaderText.Logf(TEXT("#if %s\r\n"), *ClassDef.GetEnclosingDefine()); BoilerPlateCPP.Logf(TEXT("#if %s\r\n"), *ClassDef.GetEnclosingDefine()); } Boilerplate.Logf(TEXT("\t%s(%s, %s_API)\r\n"), MacroNameHeader, *ClassCPPName, *APIArg); OutGeneratedHeaderText.Log(Macroize(*GeneratedSerializeFunctionHeaderMacroName, *Boilerplate)); BoilerPlateCPP.Logf(TEXT("\t%s(%s)\r\n"), MacroNameCPP, *ClassCPPName); if (ClassDef.GetEnclosingDefine().Len()) { OutGeneratedHeaderText.Logf(TEXT("#else\r\n")); OutGeneratedHeaderText.Log(Macroize(*GeneratedSerializeFunctionHeaderMacroName, TEXT(""))); OutGeneratedHeaderText.Logf(TEXT("#endif\r\n")); BoilerPlateCPP.Logf(TEXT("#endif\r\n")); } GeneratedSerializeFunctionCPP = BoilerPlateCPP; } { FUHTStringBuilder Boilerplate; // Export the class's native function registration. Boilerplate.Logf(TEXT("private:\r\n")); Boilerplate.Logf(TEXT("\tstatic void StaticRegisterNatives%s();\r\n"), *ClassCPPName); Boilerplate.Log(*FriendText); Boilerplate.Logf(TEXT("public:\r\n")); const bool bCastedClass = ClassDef.HasAnyCastFlags(CASTCLASS_AllFlags) && SuperClassDef && ClassDef.GetClassCastFlags() != SuperClassDef->GetClassCastFlags(); Boilerplate.Logf(TEXT("\tDECLARE_CLASS(%s, %s, COMPILED_IN_FLAGS(%s%s), %s, TEXT(\"%s\"), %s_API)\r\n"), *ClassCPPName, *SuperClassCPPName, ClassDef.HasAnyClassFlags(CLASS_Abstract) ? TEXT("CLASS_Abstract") : TEXT("0"), *GetClassFlagExportText(ClassDef), bCastedClass ? *FString::Printf(TEXT("CASTCLASS_%s"), *ClassCPPName) : TEXT("CASTCLASS_None"), *ClassDef.GetTypePackageName(), *APIArg); Boilerplate.Logf(TEXT("\tDECLARE_SERIALIZER(%s)\r\n"), *ClassCPPName); // Add the serialization function declaration if we generated one if (GeneratedSerializeFunctionHeaderMacroName.Len() > 0) { Boilerplate.Logf(TEXT("\t%s\r\n"), *GeneratedSerializeFunctionHeaderMacroName); } if (SuperClassDef && ClassDef.GetClassWithin() != SuperClassDef->GetClassWithin()) { Boilerplate.Logf(TEXT("\tDECLARE_WITHIN(%s)\r\n"), *ClassDef.GetClassWithin()->GetAlternateNameCPP()); } if (ClassDef.HasAnyClassFlags(CLASS_Interface)) { ExportConstructorsMacros(OutGeneratedHeaderText, OutCpp, StandardUObjectConstructorsMacroCall, EnhancedUObjectConstructorsMacroCall, SourceFile.GetGeneratedMacroName(ClassDef.GetGeneratedBodyLine()), ClassDef, *APIArg); FString InterfaceMacroName = SourceFile.GetGeneratedMacroName(ClassDef.GetGeneratedBodyLine(), TEXT("_GENERATED_UINTERFACE_BODY")); OutGeneratedHeaderText.Log(Macroize(*(InterfaceMacroName + TEXT("()")), *Boilerplate)); int32 ClassGeneratedBodyLine = ClassDef.GetGeneratedBodyLine(); FString DeprecationWarning = GetGeneratedMacroDeprecationWarning(TEXT("GENERATED_UINTERFACE_BODY")); const TCHAR* Offset = TEXT("\t"); OutGeneratedHeaderText.Log( Macroize( *SourceFile.GetGeneratedBodyMacroName(ClassGeneratedBodyLine, true), FString::Printf(TEXT("\t%s\t%s\t%s()" LINE_TERMINATOR_ANSI "%s\t%s") , *DeprecationWarning , DisableDeprecationWarnings , *InterfaceMacroName , *StandardUObjectConstructorsMacroCall , EnableDeprecationWarnings ) ) ); OutGeneratedHeaderText.Log( Macroize( *SourceFile.GetGeneratedBodyMacroName(ClassGeneratedBodyLine), FString::Printf(TEXT("\t%s\t%s()" LINE_TERMINATOR_ANSI "%s%s\t%s") , DisableDeprecationWarnings , *InterfaceMacroName , *EnhancedUObjectConstructorsMacroCall , *GetPreservedAccessSpecifierString(ClassDef) , EnableDeprecationWarnings ) ) ); // ============================================= // Export the pure interface version of the class // the name of the pure interface class FString InterfaceCPPName = ClassDef.GetAlternateNameCPP(true); FString SuperInterfaceCPPName = SuperClassDef ? SuperClassDef->GetAlternateNameCPP(true) : FString(); // Thunk functions FUHTStringBuilder InterfaceBoilerplate; InterfaceBoilerplate.Logf(TEXT("protected:\r\n\tvirtual ~%s() {}\r\n"), *InterfaceCPPName); InterfaceBoilerplate.Logf(TEXT("public:\r\n\ttypedef %s UClassType;\r\n"), *ClassCPPName); InterfaceBoilerplate.Logf(TEXT("\ttypedef %s ThisClass;\r\n"), *InterfaceCPPName); ExportInterfaceCallFunctions(OutCpp, InterfaceBoilerplate, OutReferenceGatherers, CallbackFunctions, *ClassDef.GetName()); // 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 (SuperClassDef->IsChildOf(*GUInterfaceDef)) { // Note: This used to be declared as a pure virtual function, but it was changed here in order to allow the Blueprint nativization process // to detect C++ interface classes that explicitly declare pure virtual functions via type traits. This code will no longer trigger that check. InterfaceBoilerplate.Logf(TEXT("\tvirtual UObject* _getUObject() const { return nullptr; }\r\n")); } if (ClassHasReplicatedProperties(ClassDef)) { WriteReplicatedMacroData(*ClassCPPName, *APIArg, ClassDef, InterfaceBoilerplate, SourceFile, OutFlags); } FString NoPureDeclsMacroName = SourceFile.GetGeneratedMacroName(ClassDef.GetGeneratedBodyLine(), TEXT("_INCLASS_IINTERFACE_NO_PURE_DECLS")); WriteMacro(OutGeneratedHeaderText, NoPureDeclsMacroName, InterfaceBoilerplate); ClassNoPureDeclsMacroCalls.Logf(TEXT("\t%s\r\n"), *NoPureDeclsMacroName); FString MacroName = SourceFile.GetGeneratedMacroName(ClassDef.GetGeneratedBodyLine(), TEXT("_INCLASS_IINTERFACE")); WriteMacro(OutGeneratedHeaderText, MacroName, InterfaceBoilerplate); ClassMacroCalls.Logf(TEXT("\t%s\r\n"), *MacroName); } else { // export the class's config name if (SuperClassDef && ClassDef.GetClassConfigName() != NAME_None && ClassDef.GetClassConfigName() != SuperClassDef->GetClassConfigName()) { Boilerplate.Logf(TEXT("\tstatic const TCHAR* StaticConfigName() {return TEXT(\"%s\");}\r\n\r\n"), *ClassDef.GetClassConfigName().ToString()); } // export implementation of _getUObject for classes that implement interfaces for (FUnrealStructDefinitionInfo::FBaseStructInfo& BaseStruct : ClassDef.GetBaseStructInfos()) { if (FUnrealClassDefinitionInfo* BaseClass = UHTCast(BaseStruct.Struct); BaseClass != nullptr && BaseClass->IsInterface()) { Boilerplate.Logf(TEXT("\tvirtual UObject* _getUObject() const override { return const_cast<%s*>(this); }\r\n"), *ClassCPPName); break; } } if (ClassHasReplicatedProperties(ClassDef)) { WriteReplicatedMacroData(*ClassCPPName, *APIArg, ClassDef, Boilerplate, SourceFile, OutFlags); } { FString NoPureDeclsMacroName = SourceFile.GetGeneratedMacroName(ClassDef.GetGeneratedBodyLine(), TEXT("_INCLASS_NO_PURE_DECLS")); WriteMacro(OutGeneratedHeaderText, NoPureDeclsMacroName, Boilerplate); ClassNoPureDeclsMacroCalls.Logf(TEXT("\t%s\r\n"), *NoPureDeclsMacroName); FString MacroName = SourceFile.GetGeneratedMacroName(ClassDef.GetGeneratedBodyLine(), TEXT("_INCLASS")); WriteMacro(OutGeneratedHeaderText, MacroName, Boilerplate); ClassMacroCalls.Logf(TEXT("\t%s\r\n"), *MacroName); ExportConstructorsMacros(OutGeneratedHeaderText, OutCpp, StandardUObjectConstructorsMacroCall, EnhancedUObjectConstructorsMacroCall, SourceFile.GetGeneratedMacroName(ClassDef.GetGeneratedBodyLine()), ClassDef, *APIArg); } } } { FString MacroName = SourceFile.GetGeneratedMacroName(ClassDef.GetPrologLine(), TEXT("_PROLOG")); WriteMacro(OutGeneratedHeaderText, MacroName, PrologMacroCalls); } { const TCHAR* Public = TEXT("public:" LINE_TERMINATOR_ANSI); const bool bIsIInterface = ClassDef.HasAnyClassFlags(CLASS_Interface); const TCHAR* MacroName; FString DeprecationWarning; FString LegacyGeneratedBody; FString GeneratedBody; int32 GeneratedBodyLine; if (bIsIInterface) { MacroName = TEXT("GENERATED_IINTERFACE_BODY()"); GeneratedBodyLine = ClassDef.GetInterfaceGeneratedBodyLine(); LegacyGeneratedBody = ClassMacroCalls; GeneratedBody = ClassNoPureDeclsMacroCalls; } else { MacroName = TEXT("GENERATED_UCLASS_BODY()"); DeprecationWarning = GetGeneratedMacroDeprecationWarning(MacroName); GeneratedBodyLine = ClassDef.GetGeneratedBodyLine(); LegacyGeneratedBody = FString::Printf(TEXT("%s%s"), *ClassMacroCalls, *StandardUObjectConstructorsMacroCall); GeneratedBody = FString::Printf(TEXT("%s%s"), *ClassNoPureDeclsMacroCalls, *EnhancedUObjectConstructorsMacroCall); } FString WrappedLegacyGeneratedBody = FString::Printf(TEXT("%s%s%s%s%s%s"), *DeprecationWarning, DisableDeprecationWarnings, Public, *LegacyGeneratedBody, Public, EnableDeprecationWarnings); FString WrappedGeneratedBody = FString::Printf(TEXT("%s%s%s%s%s"), DisableDeprecationWarnings, Public, *GeneratedBody, *GetPreservedAccessSpecifierString(ClassDef), EnableDeprecationWarnings); OutGeneratedHeaderText.Log(Macroize(*SourceFile.GetGeneratedBodyMacroName(GeneratedBodyLine, true), MoveTemp(WrappedLegacyGeneratedBody))); OutGeneratedHeaderText.Log(Macroize(*SourceFile.GetGeneratedBodyMacroName(GeneratedBodyLine, false), MoveTemp(WrappedGeneratedBody))); } // Forward declare the StaticClass specialisation in the header OutGeneratedHeaderText.Logf(TEXT("template<> %sUClass* StaticClass();\r\n\r\n"), *GetAPIString(), *ClassCPPName); // If there is a serialization function implementation for the CPP file, add it now if (GeneratedSerializeFunctionCPP.Len()) { OutCpp.Log(GeneratedSerializeFunctionCPP); } } /** * Generates private copy-constructor declaration. * * @param Out Output device to generate to. * @param Class Class to generate constructor for. * @param API API string for this constructor. */ void ExportCopyConstructorDefinition(FOutputDevice& Out, const TCHAR* API, const TCHAR* ClassCPPName) { Out.Logf(TEXT("private:\r\n")); Out.Logf(TEXT("\t/** Private move- and copy-constructors, should never be used */\r\n")); Out.Logf(TEXT("\t%s_API %s(%s&&);\r\n"), API, ClassCPPName, ClassCPPName); Out.Logf(TEXT("\t%s_API %s(const %s&);\r\n"), API, ClassCPPName, ClassCPPName); Out.Logf(TEXT("public:\r\n")); } /** * Generates vtable helper caller and eventual constructor body. * * @param Out Output device to generate to. * @param Class Class to generate for. * @param API API string. */ void ExportVTableHelperCtorAndCaller(FOutputDevice& Out, FUnrealClassDefinitionInfo& ClassDef, const TCHAR* API, const TCHAR* ClassCPPName) { if (!ClassDef.IsCustomVTableHelperConstructorDeclared()) { Out.Logf(TEXT("\tDECLARE_VTABLE_PTR_HELPER_CTOR(%s_API, %s);" LINE_TERMINATOR_ANSI), API, ClassCPPName); } Out.Logf(TEXT("\tDEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(%s);" LINE_TERMINATOR_ANSI), ClassCPPName); } /** * Generates standard constructor declaration. * * @param Out Output device to generate to. * @param Class Class to generate constructor for. * @param API API string for this constructor. */ void ExportStandardConstructorsMacro(FOutputDevice& Out, FUnrealClassDefinitionInfo& ClassDef, const TCHAR* API, const TCHAR* ClassCPPName) { if (!ClassDef.HasCustomConstructor()) { Out.Logf(TEXT("\t/** Standard constructor, called after all reflected properties have been initialized */\r\n")); Out.Logf(TEXT("\t%s_API %s(const FObjectInitializer& ObjectInitializer%s);\r\n"), API, ClassCPPName, ClassDef.IsDefaultConstructorDeclared() ? TEXT("") : TEXT(" = FObjectInitializer::Get()")); } if (ClassDef.HasAnyClassFlags(CLASS_Abstract)) { Out.Logf(TEXT("\tDEFINE_ABSTRACT_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(%s)\r\n"), ClassCPPName); } else { Out.Logf(TEXT("\tDEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(%s)\r\n"), ClassCPPName); } ExportVTableHelperCtorAndCaller(Out, ClassDef, API, ClassCPPName); ExportCopyConstructorDefinition(Out, API, ClassCPPName); } /** * Generates constructor definition. * * @param Out Output device to generate to. * @param Class Class to generate constructor for. * @param API API string for this constructor. */ void ExportConstructorDefinition(FOutputDevice& Out, FUnrealClassDefinitionInfo& ClassDef, const TCHAR* API, const TCHAR* ClassCPPName) { if (!ClassDef.IsConstructorDeclared()) { Out.Logf(TEXT("\t/** Standard constructor, called after all reflected properties have been initialized */\r\n")); // Assume super class has OI constructor, this may not always be true but we should always be able to check this. // In any case, it will default to old behaviour before we even checked this. bool bSuperClassObjectInitializerConstructorDeclared = true; FUnrealClassDefinitionInfo* SuperClassDef = ClassDef.GetSuperClass(); if (SuperClassDef != nullptr) { if (SuperClassDef->HasSource() && !SuperClassDef->GetUnrealSourceFile().IsNoExportTypes()) // Don't consider the internal types generated from the engine { // Since we are dependent on our SuperClass having determined which constructors are defined, // if it is not yet determined we will need to wait on it becoming available. // Since the SourceFile array provided to the ParallelFor is in dependency order and does not allow cyclic dependencies, // we can be certain that another thread has started processing the file containing our SuperClass before this // file would have been assigned out, so we just have to wait while (!SuperClassDef->IsConstructorDeclared()) { FPlatformProcess::Sleep(0.01); } bSuperClassObjectInitializerConstructorDeclared = SuperClassDef->IsObjectInitializerConstructorDeclared(); } } if (bSuperClassObjectInitializerConstructorDeclared) { Out.Logf(TEXT("\t%s_API %s(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { };\r\n"), API, ClassCPPName); ClassDef.MarkObjectInitializerConstructorDeclared(); } else { Out.Logf(TEXT("\t%s_API %s() { };\r\n"), API, ClassCPPName); ClassDef.MarkDefaultConstructorDeclared(); } ClassDef.MarkConstructorDeclared(); } ExportCopyConstructorDefinition(Out, API, ClassCPPName); } /** * Generates constructor call definition. * * @param Out Output device to generate to. * @param Class Class to generate constructor call definition for. */ void ExportDefaultConstructorCallDefinition(FOutputDevice& Out, FUnrealClassDefinitionInfo& ClassDef, const TCHAR* ClassCPPName) { if (ClassDef.IsObjectInitializerConstructorDeclared()) { if (ClassDef.HasAnyClassFlags(CLASS_Abstract)) { Out.Logf(TEXT("\tDEFINE_ABSTRACT_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(%s)\r\n"), ClassCPPName); } else { Out.Logf(TEXT("\tDEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(%s)\r\n"), ClassCPPName); } } else if (ClassDef.IsDefaultConstructorDeclared()) { if (ClassDef.HasAnyClassFlags(CLASS_Abstract)) { Out.Logf(TEXT("\tDEFINE_ABSTRACT_DEFAULT_CONSTRUCTOR_CALL(%s)\r\n"), ClassCPPName); } else { Out.Logf(TEXT("\tDEFINE_DEFAULT_CONSTRUCTOR_CALL(%s)\r\n"), ClassCPPName); } } else { Out.Logf(TEXT("\tDEFINE_FORBIDDEN_DEFAULT_CONSTRUCTOR_CALL(%s)\r\n"), ClassCPPName); } } /** * Generates enhanced constructor declaration. * * @param Out Output device to generate to. * @param Class Class to generate constructor for. * @param API API string for this constructor. */ void ExportEnhancedConstructorsMacro(FOutputDevice& Out, FUnrealClassDefinitionInfo& ClassDef, const TCHAR* API, const TCHAR* ClassCPPName) { ExportConstructorDefinition(Out, ClassDef, API, ClassCPPName); ExportVTableHelperCtorAndCaller(Out, ClassDef, API, ClassCPPName); ExportDefaultConstructorCallDefinition(Out, ClassDef, ClassCPPName); } /** * Gets a package relative inclusion path of the given source file for build. * * @param SourceFile Given source file. * * @returns Inclusion path. */ FString GetBuildPath(FUnrealSourceFile& SourceFile) { FString Out = SourceFile.GetFilename(); ConvertToBuildIncludePath(SourceFile.GetPackageDef().GetModule(), Out); return Out; } void FNativeClassHeaderGenerator::ExportConstructorsMacros(FOutputDevice& OutGeneratedHeaderText, FOutputDevice& Out, FOutputDevice& StandardUObjectConstructorsMacroCall, FOutputDevice& EnhancedUObjectConstructorsMacroCall, const FString& ConstructorsMacroPrefix, FUnrealClassDefinitionInfo& ClassDef, const TCHAR* APIArg) { const FString ClassCPPName = ClassDef.GetAlternateNameCPP(); FUHTStringBuilder StdMacro; FUHTStringBuilder EnhMacro; FString StdMacroName = ConstructorsMacroPrefix + TEXT("_STANDARD_CONSTRUCTORS"); FString EnhMacroName = ConstructorsMacroPrefix + TEXT("_ENHANCED_CONSTRUCTORS"); ExportStandardConstructorsMacro(StdMacro, ClassDef, APIArg, *ClassCPPName); ExportEnhancedConstructorsMacro(EnhMacro, ClassDef, APIArg, *ClassCPPName); if (!ClassDef.IsCustomVTableHelperConstructorDeclared()) { Out.Logf(TEXT("\tDEFINE_VTABLE_PTR_HELPER_CTOR(%s);" LINE_TERMINATOR_ANSI), *ClassCPPName); } OutGeneratedHeaderText.Log(Macroize(*StdMacroName, *StdMacro)); OutGeneratedHeaderText.Log(Macroize(*EnhMacroName, *EnhMacro)); StandardUObjectConstructorsMacroCall.Logf(TEXT("\t%s\r\n"), *StdMacroName); EnhancedUObjectConstructorsMacroCall.Logf(TEXT("\t%s\r\n"), *EnhMacroName); } bool FNativeClassHeaderGenerator::WriteHeader(FGeneratedFileInfo& FileInfo, const FString& InBodyText, const TSet& InAdditionalHeaders, const TSet& ForwardDeclarations) { FUHTStringBuilder GeneratedHeaderTextWithCopyright; GeneratedHeaderTextWithCopyright.Log(HeaderCopyright); GeneratedHeaderTextWithCopyright.Log(TEXT("#include \"UObject/ObjectMacros.h\"\r\n")); GeneratedHeaderTextWithCopyright.Log(TEXT("#include \"UObject/ScriptMacros.h\"\r\n")); for (const FString& AdditionalHeader : InAdditionalHeaders) { GeneratedHeaderTextWithCopyright.Logf(TEXT("#include \"%s\"\r\n"), *AdditionalHeader); } GeneratedHeaderTextWithCopyright.Log(LINE_TERMINATOR); GeneratedHeaderTextWithCopyright.Log(DisableDeprecationWarnings); if (ForwardDeclarations.Num() > 0) { TArray Sorted; Sorted.Reserve(ForwardDeclarations.Num()); for (const FString& Ref : ForwardDeclarations) { if (Ref.Len() > 0) { Sorted.Add(&Ref); } } Sorted.Sort(); for (const FString* Ref : Sorted) { GeneratedHeaderTextWithCopyright.Logf(TEXT("%s\r\n"), **Ref); } } GeneratedHeaderTextWithCopyright.Log(InBodyText); GeneratedHeaderTextWithCopyright.Log(EnableDeprecationWarnings); const bool bHasChanged = SaveHeaderIfChanged(FileInfo, MoveTemp(GeneratedHeaderTextWithCopyright)); return bHasChanged; } /** * 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(FUnrealClassDefinitionInfo& ClassDef) { FString StaticClassFlagText; if (ClassDef.HasAnyClassFlags(CLASS_Transient) ) { StaticClassFlagText += TEXT(" | CLASS_Transient"); } if (ClassDef.HasAnyClassFlags(CLASS_Optional)) { StaticClassFlagText += TEXT(" | CLASS_Optional"); } if (ClassDef.HasAnyClassFlags(CLASS_DefaultConfig) ) { StaticClassFlagText += TEXT(" | CLASS_DefaultConfig"); } if (ClassDef.HasAnyClassFlags(CLASS_GlobalUserConfig) ) { StaticClassFlagText += TEXT(" | CLASS_GlobalUserConfig"); } if (ClassDef.HasAnyClassFlags(CLASS_ProjectUserConfig)) { StaticClassFlagText += TEXT(" | CLASS_ProjectUserConfig"); } if (ClassDef.HasAnyClassFlags(CLASS_Config) ) { StaticClassFlagText += TEXT(" | CLASS_Config"); } if (ClassDef.HasAnyClassFlags(CLASS_Interface) ) { StaticClassFlagText += TEXT(" | CLASS_Interface"); } if (ClassDef.HasAnyClassFlags(CLASS_Deprecated) ) { StaticClassFlagText += TEXT(" | CLASS_Deprecated"); } return StaticClassFlagText; } /** * Exports the header text for the list of enums specified * * @param Enums the enums to export */ void FNativeClassHeaderGenerator::ExportEnum(FOutputDevice& Out, FUnrealEnumDefinitionInfo& EnumDef) const { // Export FOREACH macro Out.Logf( TEXT("#define FOREACH_ENUM_%s(op) "), *EnumDef.GetName().ToUpper() ); bool bHasExistingMax = EnumDef.ContainsExistingMax(); int64 MaxEnumVal = bHasExistingMax ? EnumDef.GetMaxEnumValue() : 0; for (int32 i = 0; i < EnumDef.NumEnums(); i++) { if (bHasExistingMax && EnumDef.GetValueByIndex(i) == MaxEnumVal) { continue; } const FString QualifiedEnumValue = EnumDef.GetNameByIndex(i).ToString(); Out.Logf( TEXT("\\\r\n\top(%s) "), *QualifiedEnumValue ); } Out.Logf( TEXT("\r\n") ); // Forward declare the StaticEnum<> specialisation for enum classes if (EnumDef.GetCppForm() == UEnum::ECppForm::EnumClass) { FString UnderlyingTypeString; if (EnumDef.GetUnderlyingType() != EUnderlyingEnumType::Unspecified) { UnderlyingTypeString = TEXT(" : "); switch (EnumDef.GetUnderlyingType()) { case EUnderlyingEnumType::int8: UnderlyingTypeString += TNameOf::GetName(); break; case EUnderlyingEnumType::int16: UnderlyingTypeString += TNameOf::GetName(); break; case EUnderlyingEnumType::int32: UnderlyingTypeString += TNameOf::GetName(); break; case EUnderlyingEnumType::int64: UnderlyingTypeString += TNameOf::GetName(); break; case EUnderlyingEnumType::uint8: UnderlyingTypeString += TNameOf::GetName(); break; case EUnderlyingEnumType::uint16: UnderlyingTypeString += TNameOf::GetName(); break; case EUnderlyingEnumType::uint32: UnderlyingTypeString += TNameOf::GetName(); break; case EUnderlyingEnumType::uint64: UnderlyingTypeString += TNameOf::GetName(); break; default: check(false); } } Out.Logf( TEXT("\r\n") ); Out.Logf( TEXT("enum class %s%s;\r\n"), *EnumDef.GetCppType(), *UnderlyingTypeString ); Out.Logf( TEXT("template<> %sUEnum* StaticEnum<%s>();\r\n"), *GetAPIString(), *EnumDef.GetCppType()); Out.Logf( TEXT("\r\n") ); } } // Exports the header text for the list of structs specified (GENERATED_BODY impls) void FNativeClassHeaderGenerator::ExportGeneratedStructBodyMacros(FOutputDevice& OutGeneratedHeaderText, FOutputDevice& Out, FReferenceGatherers& OutReferenceGatherers, const FUnrealSourceFile& SourceFile, FUnrealScriptStructDefinitionInfo& ScriptStructDef) const { ScriptStructDef.AddCrossModuleReference(OutReferenceGatherers.UniqueCrossModuleReferences, true); const FString ActualStructName = ScriptStructDef.GetName(); const FString& FriendApiString = GetAPIString(); FUnrealStructDefinitionInfo* BaseStructDef = ScriptStructDef.GetSuperStruct(); const FString StructNameCPP = ScriptStructDef.GetAlternateNameCPP(); const FString& SingletonName = ScriptStructDef.GetSingletonName(true); const FString& ChoppedSingletonName = ScriptStructDef.GetSingletonNameChopped(true); const FString RigVMParameterPrefix = TEXT("FRigVMExecuteContext& RigVMExecuteContext"); TArray RigVMVirtualFuncProlog, RigVMVirtualFuncEpilog, RigVMStubProlog; // for RigVM methods we need to generated a macro used for implementing the static method // and prepare two prologs: one for the virtual function implementation, and one for the stub // invoking the static method. const FRigVMStructInfo& StructRigVMInfo = ScriptStructDef.GetRigVMInfo(); if(StructRigVMInfo.bHasRigVM) { //RigVMStubProlog.Add(FString::Printf(TEXT("ensure(RigVMOperandMemory.Num() == %d);"), StructRigVMInfo->Members.Num())); int32 OperandIndex = 0; for (int32 ParameterIndex = 0; ParameterIndex < StructRigVMInfo.Members.Num(); ParameterIndex++) { const FRigVMParameter& Parameter = StructRigVMInfo.Members[ParameterIndex]; if(Parameter.RequiresCast()) { RigVMVirtualFuncProlog.Add(FString::Printf(TEXT("%s %s(%s);"), *Parameter.CastType, *Parameter.CastName, *Parameter.Name)); } const FString& ParamTypeOriginal = Parameter.TypeOriginal(true); const FString& ParamNameOriginal = Parameter.NameOriginal(false); FString AdditionalParameters; if(!Parameter.bInput && !Parameter.bOutput && !Parameter.bSingleton) { static const FString SliceContextParameter = TEXT(", RigVMExecuteContext.GetSlice().GetIndex()"); AdditionalParameters = SliceContextParameter; } if (Parameter.IsArray()) { FString ExtendedType = Parameter.ExtendedType(); RigVMStubProlog.Add(FString::Printf(TEXT("TArray%s& %s = *(TArray%s*)RigVMMemoryHandles[%d].GetData(false%s);"), *ExtendedType, *ParamNameOriginal, *ExtendedType, OperandIndex, *AdditionalParameters)); OperandIndex++; } else { FString VariableType = Parameter.TypeVariableRef(true); FString ExtractedType = Parameter.TypeOriginal(); FString ParameterCast = FString::Printf(TEXT("*(%s*)"), *ExtractedType); RigVMStubProlog.Add(FString::Printf(TEXT("%s %s = %sRigVMMemoryHandles[%d].GetData(false%s);"), *VariableType, *ParamNameOriginal, *ParameterCast, OperandIndex, *AdditionalParameters)); OperandIndex++; } } FString StructMembers = StructRigVMInfo.Members.Declarations(false, TEXT(", \\\r\n\t\t"), true, false); OutGeneratedHeaderText.Log(TEXT("\r\n")); for (const FRigVMMethodInfo& MethodInfo : StructRigVMInfo.Methods) { FString ParameterSuffix = MethodInfo.Parameters.Declarations(true, TEXT(", \\\r\n\t\t")); FString RigVMParameterPrefix2 = RigVMParameterPrefix + FString((StructMembers.IsEmpty() && ParameterSuffix.IsEmpty()) ? TEXT("") : TEXT(", \\\r\n\t\t")); OutGeneratedHeaderText.Logf(TEXT("#define %s_%s() \\\r\n"), *StructNameCPP, *MethodInfo.Name); OutGeneratedHeaderText.Logf(TEXT("\t%s %s::Static%s( \\\r\n\t\t%s%s%s \\\r\n\t)\r\n"), *MethodInfo.ReturnType, *StructNameCPP, *MethodInfo.Name, *RigVMParameterPrefix2, *StructMembers, *ParameterSuffix); } OutGeneratedHeaderText.Log(TEXT("\r\n")); } // Export struct. if (ScriptStructDef.HasAnyStructFlags(STRUCT_Native)) { check(ScriptStructDef.GetMacroDeclaredLineNumber() != INDEX_NONE); const bool bRequiredAPI = !ScriptStructDef.HasAnyStructFlags(STRUCT_RequiredAPI); const FString FriendLine = FString::Printf(TEXT("\tfriend struct %s_Statics;\r\n"), *ChoppedSingletonName); const FString StaticClassLine = FString::Printf(TEXT("\t%sstatic class UScriptStruct* StaticStruct();\r\n"), (bRequiredAPI ? *FriendApiString : TEXT(""))); // if we have RigVM methods on this struct we need to // declare the static method as well as the stub method FString RigVMMethodsDeclarations; if (StructRigVMInfo.bHasRigVM) { FString StructMembers = StructRigVMInfo.Members.Declarations(false, TEXT(",\r\n\t\t"), true, false); for (const FRigVMMethodInfo& MethodInfo : StructRigVMInfo.Methods) { FString StructMembersForStub = StructRigVMInfo.Members.Names(false, TEXT(",\r\n\t\t\t"), false); FString ParameterSuffix = MethodInfo.Parameters.Declarations(true, TEXT(",\r\n\t\t")); FString ParameterNamesSuffix = MethodInfo.Parameters.Names(true, TEXT(",\r\n\t\t\t")); FString RigVMParameterPrefix2 = RigVMParameterPrefix + FString((StructMembers.IsEmpty() && ParameterSuffix.IsEmpty()) ? TEXT("") : TEXT(",\r\n\t\t")); FString RigVMParameterPrefix4 = FString(TEXT("RigVMExecuteContext")) + FString((StructMembersForStub.IsEmpty() && ParameterSuffix.IsEmpty()) ? TEXT("") : TEXT(",\r\n\t\t\t")); RigVMMethodsDeclarations += FString::Printf(TEXT("\tstatic %s Static%s(\r\n\t\t%s%s%s\r\n\t);\r\n"), *MethodInfo.ReturnType, *MethodInfo.Name, *RigVMParameterPrefix2, *StructMembers, *ParameterSuffix); RigVMMethodsDeclarations += FString::Printf(TEXT("\tFORCEINLINE_DEBUGGABLE static %s RigVM%s(\r\n\t\t%s,\r\n\t\tFRigVMMemoryHandleArray RigVMMemoryHandles\r\n\t)\r\n"), *MethodInfo.ReturnType, *MethodInfo.Name, *RigVMParameterPrefix); RigVMMethodsDeclarations += FString::Printf(TEXT("\t{\r\n")); // implement inline stub method body if (MethodInfo.Parameters.Num() > 0) { //RigVMMethodsDeclarations += FString::Printf(TEXT("\t\tensure(RigVMUserData.Num() == %d);\r\n"), MethodInfo.Parameters.Num()); for (int32 ParameterIndex = 0; ParameterIndex < MethodInfo.Parameters.Num(); ParameterIndex++) { const FRigVMParameter& Parameter = MethodInfo.Parameters[ParameterIndex]; RigVMMethodsDeclarations += FString::Printf(TEXT("\t\t%s = *(%s*)RigVMExecuteContext.OpaqueArguments[%d];\r\n"), *Parameter.Declaration(), *Parameter.TypeNoRef(), ParameterIndex); } RigVMMethodsDeclarations += FString::Printf(TEXT("\t\t\r\n")); } if (RigVMStubProlog.Num() > 0) { for (const FString& RigVMStubPrologLine : RigVMStubProlog) { RigVMMethodsDeclarations += FString::Printf(TEXT("\t\t%s\r\n"), *RigVMStubPrologLine); } RigVMMethodsDeclarations += FString::Printf(TEXT("\t\t\r\n")); } RigVMMethodsDeclarations += FString::Printf(TEXT("\t\t%sStatic%s(\r\n\t\t\t%s%s%s\r\n\t\t);\r\n"), *MethodInfo.ReturnPrefix(), *MethodInfo.Name, *RigVMParameterPrefix4, *StructMembersForStub, *ParameterNamesSuffix); RigVMMethodsDeclarations += FString::Printf(TEXT("\t}\r\n")); } } const FString SuperTypedef = BaseStructDef ? FString::Printf(TEXT("\ttypedef %s Super;\r\n"), *BaseStructDef->GetAlternateNameCPP()) : FString(); FString CombinedLine = FString::Printf(TEXT("%s%s%s%s"), *FriendLine, *StaticClassLine, *RigVMMethodsDeclarations, *SuperTypedef); const FString MacroName = SourceFile.GetGeneratedBodyMacroName(ScriptStructDef.GetMacroDeclaredLineNumber()); const FString Macroized = Macroize(*MacroName, MoveTemp(CombinedLine)); OutGeneratedHeaderText.Log(Macroized); // Inject static assert to verify that we do not add vtable if (BaseStructDef) { FString BaseStructNameCPP = BaseStructDef->GetAlternateNameCPP(); FString VerifyPolymorphicStructString = FString::Printf(TEXT("\r\nstatic_assert(std::is_polymorphic<%s>() == std::is_polymorphic<%s>(), \"USTRUCT %s cannot be polymorphic unless super %s is polymorphic\");\r\n\r\n"), *StructNameCPP, *BaseStructNameCPP, *StructNameCPP, *BaseStructNameCPP); Out.Log(VerifyPolymorphicStructString); } FString GetHashName = FString::Printf(TEXT("Get_%s_Hash"), *ChoppedSingletonName); Out.Logf(TEXT("\tstatic FStructRegistrationInfo Z_Registration_Info_UScriptStruct_%s;\r\n"), *ScriptStructDef.GetName()); Out.Logf(TEXT("class UScriptStruct* %s::StaticStruct()\r\n"), *StructNameCPP); Out.Logf(TEXT("{\r\n")); // UStructs can have UClass or UPackage outer (if declared in non-UClass headers). const FString& OuterName (GetPackageSingletonName(UHTCastChecked(ScriptStructDef.GetOuter()), OutReferenceGatherers.UniqueCrossModuleReferences)); FString OuterSingletonName = FString::Printf(TEXT("Z_Registration_Info_UScriptStruct_%s.OuterSingleton"), *ScriptStructDef.GetName()); Out.Logf(TEXT("\tif (!%s)\r\n"), *OuterSingletonName); Out.Logf(TEXT("\t{\r\n")); Out.Logf(TEXT("\t\t%s = GetStaticStruct(%s, %s, TEXT(\"%s\"));\r\n"), *OuterSingletonName, *ChoppedSingletonName, *OuterName, *ActualStructName); // if this struct has RigVM methods - we need to register the method to our central // registry on construction of the static struct if (StructRigVMInfo.bHasRigVM) { for (const FRigVMMethodInfo& MethodInfo : StructRigVMInfo.Methods) { Out.Logf(TEXT("\t\tFRigVMRegistry::Get().Register(TEXT(\"%s::%s\"), &%s::RigVM%s, %s);\r\n"), *StructNameCPP, *MethodInfo.Name, *StructNameCPP, *MethodInfo.Name, *OuterSingletonName); } } Out.Logf(TEXT("\t}\r\n")); Out.Logf(TEXT("\treturn %s;\r\n"), *OuterSingletonName); Out.Logf(TEXT("}\r\n")); // Forward declare the StaticStruct specialization in the header OutGeneratedHeaderText.Logf(TEXT("template<> %sUScriptStruct* StaticStruct();\r\n\r\n"), *GetAPIString(), *StructNameCPP); // Generate the StaticStruct specialization Out.Logf(TEXT("template<> %sUScriptStruct* StaticStruct<%s>()\r\n"), *GetAPIString(), *StructNameCPP); Out.Logf(TEXT("{\r\n")); Out.Logf(TEXT("\treturn %s::StaticStruct();\r\n"), *StructNameCPP); Out.Logf(TEXT("}\r\n")); } FString StaticsStructName = ChoppedSingletonName + TEXT("_Statics"); FUHTStringBuilder GeneratedStructRegisterFunctionText; FUHTStringBuilder StaticDefinitions; GeneratedStructRegisterFunctionText.Logf(TEXT("\tstruct %s\r\n"), *StaticsStructName); GeneratedStructRegisterFunctionText.Logf(TEXT("\t{\r\n")); // if this is a no export struct, we will put a local struct here for offset determination TArray NoExportStructs = FindNoExportStructs(&ScriptStructDef); for (FUnrealScriptStructDefinitionInfo* NoExportStructDef : NoExportStructs) { ExportMirrorsForNoexportStruct(GeneratedStructRegisterFunctionText, *NoExportStructDef, /*Indent=*/ 2); } if (BaseStructDef) { UHTCastChecked(BaseStructDef); // this better actually be a script struct BaseStructDef->AddCrossModuleReference(OutReferenceGatherers.UniqueCrossModuleReferences, true); } EStructFlags UncomputedFlags = (EStructFlags)(ScriptStructDef.GetStructFlags() & ~STRUCT_ComputedFlags); FString OuterFunc = GetPackageSingletonNameFuncAddr(UHTCastChecked(ScriptStructDef.GetOuter()), OutReferenceGatherers.UniqueCrossModuleReferences); FString MetaDataParams = OutputMetaDataCodeForObject(GeneratedStructRegisterFunctionText, StaticDefinitions, ScriptStructDef, *FString::Printf(TEXT("%s::Struct_MetaDataParams"), *StaticsStructName), TEXT("\t\t"), TEXT("\t")); FString NewStructOps; if (ScriptStructDef.HasAnyStructFlags(STRUCT_Native)) { GeneratedStructRegisterFunctionText.Log(TEXT("\t\tstatic void* NewStructOps();\r\n")); StaticDefinitions.Logf(TEXT("\tvoid* %s::NewStructOps()\r\n"), *StaticsStructName); StaticDefinitions.Log (TEXT("\t{\r\n")); StaticDefinitions.Logf(TEXT("\t\treturn (UScriptStruct::ICppStructOps*)new UScriptStruct::TCppStructOps<%s>();\r\n"), *StructNameCPP); StaticDefinitions.Log (TEXT("\t}\r\n")); NewStructOps = TEXT("&NewStructOps"); } else { NewStructOps = TEXT("nullptr"); } TTuple PropertyRange = OutputProperties(GeneratedStructRegisterFunctionText, StaticDefinitions, OutReferenceGatherers, *FString::Printf(TEXT("%s::"), *StaticsStructName), ScriptStructDef, TEXT("\t\t"), TEXT("\t")); GeneratedStructRegisterFunctionText.Log (TEXT("\t\tstatic const UECodeGen_Private::FStructParams ReturnStructParams;\r\n")); StaticDefinitions.Logf(TEXT("\tconst UECodeGen_Private::FStructParams %s::ReturnStructParams = {\r\n"), *StaticsStructName); StaticDefinitions.Logf(TEXT("\t\t(UObject* (*)())%s,\r\n"), *OuterFunc); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), *GetSingletonNameFuncAddr(BaseStructDef, OutReferenceGatherers.UniqueCrossModuleReferences)); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), *NewStructOps); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), *CreateUTF8LiteralString(ActualStructName)); StaticDefinitions.Logf(TEXT("\t\tsizeof(%s),\r\n"), *StructNameCPP); StaticDefinitions.Logf(TEXT("\t\talignof(%s),\r\n"), *StructNameCPP); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), *PropertyRange.Get<0>()); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), *PropertyRange.Get<1>()); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), TEXT("RF_Public|RF_Transient|RF_MarkAsNative")); StaticDefinitions.Logf(TEXT("\t\tEStructFlags(0x%08X),\r\n"), (uint32)UncomputedFlags); StaticDefinitions.Logf(TEXT("\t\t%s\r\n"), *MetaDataParams); StaticDefinitions.Log (TEXT("\t};\r\n")); GeneratedStructRegisterFunctionText.Log (TEXT("\t};\r\n")); GeneratedStructRegisterFunctionText.Log(StaticDefinitions); GeneratedStructRegisterFunctionText.Logf(TEXT("\tUScriptStruct* %s\r\n"), *SingletonName); GeneratedStructRegisterFunctionText.Log (TEXT("\t{\r\n")); FString NoExportStructNameCPP; if (NoExportStructs.Contains(&ScriptStructDef)) { NoExportStructNameCPP = FString::Printf(TEXT("%s::%s"), *StaticsStructName, *StructNameCPP); } else { NoExportStructNameCPP = StructNameCPP; } FString HashFuncName = FString::Printf(TEXT("Get_%s_Hash"), *SingletonName.Replace(TEXT("()"), TEXT(""), ESearchCase::CaseSensitive)); // Structs can either have a UClass or UPackage as outer (if declared in non-UClass header). FString InnerSingletonName; if (ScriptStructDef.HasAnyStructFlags(STRUCT_Native)) { InnerSingletonName = FString::Printf(TEXT("Z_Registration_Info_UScriptStruct_%s.InnerSingleton"), *ScriptStructDef.GetName()); } else { GeneratedStructRegisterFunctionText.Logf(TEXT("\t\tstatic UScriptStruct* ReturnStruct = nullptr;\r\n")); InnerSingletonName = TEXT("ReturnStruct"); } GeneratedStructRegisterFunctionText.Logf(TEXT("\t\tif (!%s)\r\n"), *InnerSingletonName); GeneratedStructRegisterFunctionText.Log (TEXT("\t\t{\r\n")); GeneratedStructRegisterFunctionText.Logf(TEXT("\t\t\tUECodeGen_Private::ConstructUScriptStruct(%s, %s::ReturnStructParams);\r\n"), *InnerSingletonName, *StaticsStructName); GeneratedStructRegisterFunctionText.Log (TEXT("\t\t}\r\n")); GeneratedStructRegisterFunctionText.Logf(TEXT("\t\treturn %s;\r\n"), *InnerSingletonName); GeneratedStructRegisterFunctionText.Log (TEXT("\t}\r\n")); uint32 StructHash = GenerateTextHash(*GeneratedStructRegisterFunctionText); ScriptStructDef.SetHash(StructHash); Out.Log(GeneratedStructRegisterFunctionText); // if this struct has RigVM methods we need to implement both the // virtual function as well as the stub method here. // The static method is implemented by the user using a macro. if (StructRigVMInfo.bHasRigVM) { FString StructMembersForVirtualFunc = StructRigVMInfo.Members.Names(false, TEXT(",\r\n\t\t"), true); for (const FRigVMMethodInfo& MethodInfo : StructRigVMInfo.Methods) { Out.Log(TEXT("\r\n")); FString ParameterDeclaration = MethodInfo.Parameters.Declarations(false, TEXT(",\r\n\t\t")); FString ParameterSuffix = MethodInfo.Parameters.Names(true, TEXT(",\r\n\t\t")); FString RigVMParameterPrefix2 = RigVMParameterPrefix + FString((StructMembersForVirtualFunc.IsEmpty() && ParameterSuffix.IsEmpty()) ? TEXT("") : TEXT(",\r\n\t\t")); FString RigVMParameterPrefix3 = FString(TEXT("RigVMExecuteContext")) + FString((StructMembersForVirtualFunc.IsEmpty() && ParameterSuffix.IsEmpty()) ? TEXT("") : TEXT(",\r\n\t\t")); // implement the virtual function body. Out.Logf(TEXT("%s %s::%s(%s)\r\n"), *MethodInfo.ReturnType, *StructNameCPP, *MethodInfo.Name, *ParameterDeclaration); Out.Log(TEXT("{\r\n")); Out.Log(TEXT("\tFRigVMExecuteContext RigVMExecuteContext;\r\n")); if(RigVMVirtualFuncProlog.Num() > 0) { for (const FString& RigVMVirtualFuncPrologLine : RigVMVirtualFuncProlog) { Out.Logf(TEXT("\t%s\r\n"), *RigVMVirtualFuncPrologLine); } Out.Log(TEXT("\t\r\n")); } Out.Logf(TEXT("\t%sStatic%s(\r\n\t\t%s%s%s\r\n\t);\r\n"), *MethodInfo.ReturnPrefix(), *MethodInfo.Name, *RigVMParameterPrefix3, *StructMembersForVirtualFunc, *ParameterSuffix); if (RigVMVirtualFuncEpilog.Num() > 0) { for (const FString& RigVMVirtualFuncEpilogLine : RigVMVirtualFuncEpilog) { Out.Logf(TEXT("\t%s\r\n"), *RigVMVirtualFuncEpilogLine); } Out.Log(TEXT("\t\r\n")); } Out.Log(TEXT("}\r\n")); } Out.Log(TEXT("\r\n")); } } void FNativeClassHeaderGenerator::ExportGeneratedEnumInitCode(FOutputDevice& Out, FReferenceGatherers& OutReferenceGatherers, const FUnrealSourceFile& SourceFile, FUnrealEnumDefinitionInfo& EnumDef) const { const FString& SingletonName = EnumDef.GetSingletonNameChopped(true); const FString EnumNameCpp = EnumDef.GetName(); //UserDefinedEnum should already have a valid cpp name. const FString StaticsStructName = SingletonName + TEXT("_Statics"); const bool bIsEditorOnlyDataType = EnumDef.IsEditorOnly(); EnumDef.AddCrossModuleReference(OutReferenceGatherers.UniqueCrossModuleReferences, true); FMacroBlockEmitter EditorOnlyData(Out, TEXT("WITH_EDITORONLY_DATA")); EditorOnlyData(bIsEditorOnlyDataType); const FString& PackageSingletonName = GetPackageSingletonName(UHTCastChecked(EnumDef.GetOuter()), OutReferenceGatherers.UniqueCrossModuleReferences); // If we don't have a zero 0 then we emit a static assert to verify we have one if (!EnumDef.IsValidEnumValue(0) && EnumDef.HasMetaData(FHeaderParserNames::NAME_BlueprintType)) { bool bHasUnparsedValue = false; for (const TPair& Enum : EnumDef.GetEnums()) { if (Enum.Value == -1) { bHasUnparsedValue = true; break; } } if (bHasUnparsedValue) { Out.Logf(TEXT("\tstatic_assert(")); bool bDoneFirst = false; for (const TPair& Enum : EnumDef.GetEnums()) { if (Enum.Value == -1) { if (bDoneFirst) { Out.Logf(TEXT("||")); } bDoneFirst = true; Out.Logf(TEXT("!int64(%s)"), *Enum.Key.ToString()); } } Out.Logf(TEXT(", \"'%s' does not have a 0 entry!(This is a problem when the enum is initalized by default)\");\r\n"), *EnumDef.GetName()); } } Out.Logf(TEXT("\tstatic FEnumRegistrationInfo Z_Registration_Info_UEnum_%s;\r\n"), *EnumNameCpp); Out.Logf(TEXT("\tstatic UEnum* %s_StaticEnum()\r\n"), *EnumNameCpp); Out.Logf(TEXT("\t{\r\n")); Out.Logf(TEXT("\t\tif (!Z_Registration_Info_UEnum_%s.OuterSingleton)\r\n"), *EnumNameCpp); Out.Logf(TEXT("\t\t{\r\n")); Out.Logf(TEXT("\t\t\tZ_Registration_Info_UEnum_%s.OuterSingleton = GetStaticEnum(%s, %s, TEXT(\"%s\"));\r\n"), *EnumNameCpp, *SingletonName, *PackageSingletonName, *EnumNameCpp); Out.Logf(TEXT("\t\t}\r\n")); Out.Logf(TEXT("\t\treturn Z_Registration_Info_UEnum_%s.OuterSingleton;\r\n"), *EnumNameCpp); Out.Logf(TEXT("\t}\r\n")); const FString& EnumSingletonName = EnumDef.GetSingletonName(true); const FString HashFuncName = FString::Printf(TEXT("Get_%s_Hash"), *SingletonName); Out.Logf(TEXT("\ttemplate<> %sUEnum* StaticEnum<%s>()\r\n"), *GetAPIString(), *EnumDef.GetCppType()); Out.Logf(TEXT("\t{\r\n")); Out.Logf(TEXT("\t\treturn %s_StaticEnum();\r\n"), *EnumNameCpp); Out.Logf(TEXT("\t}\r\n")); FUHTStringBuilder StaticDefinitions; FUHTStringBuilder StaticDeclarations; // Generate the static declarations and definitions { // Enums can either have a UClass or UPackage as outer (if declared in non-UClass header). FString OuterString = PackageSingletonName; const TCHAR* UEnumObjectFlags = TEXT("RF_Public|RF_Transient|RF_MarkAsNative"); const TCHAR* EnumFlags = EnumDef.HasAnyEnumFlags(EEnumFlags::Flags) ? TEXT("EEnumFlags::Flags") : TEXT("EEnumFlags::None"); const TCHAR* EnumFormStr = TEXT(""); switch (EnumDef.GetCppForm()) { case UEnum::ECppForm::Regular: EnumFormStr = TEXT("UEnum::ECppForm::Regular"); break; case UEnum::ECppForm::Namespaced: EnumFormStr = TEXT("UEnum::ECppForm::Namespaced"); break; case UEnum::ECppForm::EnumClass: EnumFormStr = TEXT("UEnum::ECppForm::EnumClass"); break; } const FString& EnumDisplayNameFn = EnumDef.GetMetaData(TEXT("EnumDisplayNameFn")); StaticDeclarations.Logf(TEXT("\tstruct %s\r\n"), *StaticsStructName); StaticDeclarations.Logf(TEXT("\t{\r\n")); StaticDeclarations.Logf(TEXT("\t\tstatic const UECodeGen_Private::FEnumeratorParam Enumerators[];\r\n")); StaticDefinitions.Logf(TEXT("\tconst UECodeGen_Private::FEnumeratorParam %s::Enumerators[] = {\r\n"), *StaticsStructName); for (int32 Index = 0; Index != EnumDef.NumEnums(); ++Index) { const TCHAR* OverridenNameMetaDatakey = TEXT("OverrideName"); const FString KeyName = EnumDef.HasMetaData(OverridenNameMetaDatakey, Index) ? EnumDef.GetMetaData(OverridenNameMetaDatakey, Index) : EnumDef.GetNameByIndex(Index).ToString(); StaticDefinitions.Logf(TEXT("\t\t{ %s, (int64)%s },\r\n"), *CreateUTF8LiteralString(KeyName), *EnumDef.GetNameByIndex(Index).ToString()); } StaticDefinitions.Logf(TEXT("\t};\r\n")); FString MetaDataParamsName = StaticsStructName + TEXT("::Enum_MetaDataParams"); FString MetaDataParams = OutputMetaDataCodeForObject(StaticDeclarations, StaticDefinitions, EnumDef, *MetaDataParamsName, TEXT("\t\t"), TEXT("\t")); StaticDeclarations.Logf(TEXT("\t\tstatic const UECodeGen_Private::FEnumParams EnumParams;\r\n")); StaticDefinitions.Logf(TEXT("\tconst UECodeGen_Private::FEnumParams %s::EnumParams = {\r\n"), *StaticsStructName); StaticDefinitions.Logf(TEXT("\t\t(UObject*(*)())%s,\r\n"), *OuterString.LeftChop(2)); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), EnumDisplayNameFn.IsEmpty() ? TEXT("nullptr") : *EnumDisplayNameFn); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), *CreateUTF8LiteralString(EnumNameCpp)); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), *CreateUTF8LiteralString(EnumDef.GetCppType())); StaticDefinitions.Logf(TEXT("\t\t%s::Enumerators,\r\n"), *StaticsStructName); StaticDefinitions.Logf(TEXT("\t\tUE_ARRAY_COUNT(%s::Enumerators),\r\n"), *StaticsStructName); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), UEnumObjectFlags); StaticDefinitions.Logf(TEXT("\t\t%s,\r\n"), EnumFlags); StaticDefinitions.Logf(TEXT("\t\t(uint8)%s,\r\n"), EnumFormStr); StaticDefinitions.Logf(TEXT("\t\t%s\r\n"), *MetaDataParams); StaticDefinitions.Logf(TEXT("\t};\r\n")); StaticDeclarations.Logf(TEXT("\t};\r\n")); } ////////////////////////////////////// FUHTStringBuilder GeneratedEnumRegisterFunctionText; GeneratedEnumRegisterFunctionText.Log(*StaticDeclarations); GeneratedEnumRegisterFunctionText.Log(*StaticDefinitions); GeneratedEnumRegisterFunctionText.Logf(TEXT("\tUEnum* %s\r\n"), *EnumSingletonName); GeneratedEnumRegisterFunctionText.Logf(TEXT("\t{\r\n")); // Enums can either have a UClass or UPackage as outer (if declared in non-UClass header). FString InnerSingletonName = FString::Printf(TEXT("Z_Registration_Info_UEnum_%s.InnerSingleton"), *EnumNameCpp); GeneratedEnumRegisterFunctionText.Logf(TEXT("\t\tif (!%s)\r\n"), *InnerSingletonName); GeneratedEnumRegisterFunctionText.Logf(TEXT("\t\t{\r\n")); GeneratedEnumRegisterFunctionText.Logf(TEXT("\t\t\tUECodeGen_Private::ConstructUEnum(%s, %s::EnumParams);\r\n"), *InnerSingletonName, *StaticsStructName); GeneratedEnumRegisterFunctionText.Logf(TEXT("\t\t}\r\n")); GeneratedEnumRegisterFunctionText.Logf(TEXT("\t\treturn %s;\r\n"), *InnerSingletonName); GeneratedEnumRegisterFunctionText.Logf(TEXT("\t}\r\n")); uint32 EnumHash = GenerateTextHash(*GeneratedEnumRegisterFunctionText); EnumDef.SetHash(EnumHash); Out.Log(GeneratedEnumRegisterFunctionText); } void FNativeClassHeaderGenerator::ExportMirrorsForNoexportStruct(FOutputDevice& Out, FUnrealScriptStructDefinitionInfo& ScriptStructDef, int32 TextIndent) { // Export struct. const FString StructName = ScriptStructDef.GetAlternateNameCPP(); Out.Logf(TEXT("%sstruct %s"), FCString::Tab(TextIndent), *StructName); if (FUnrealStructDefinitionInfo* SuperStructDef = ScriptStructDef.GetSuperStruct()) { Out.Logf(TEXT(" : public %s"), *SuperStructDef->GetAlternateNameCPP()); } Out.Logf(TEXT("\r\n%s{\r\n"), FCString::Tab(TextIndent)); // Export the struct's CPP properties. ExportProperties(Out, ScriptStructDef, TextIndent); Out.Logf(TEXT("%s};\r\n\r\n"), FCString::Tab(TextIndent)); } bool FNativeClassHeaderGenerator::WillExportEventParms(FUnrealFunctionDefinitionInfo& FunctionDef) { const TArray>& Properties = FunctionDef.GetProperties(); return Properties.Num() > 0 && Properties[0]->HasAnyPropertyFlags(CPF_Parm); } void WriteEventFunctionPrologue(FOutputDevice& Output, int32 Indent, const FParmsAndReturnProperties& Parameters, FUnrealObjectDefinitionInfo& FunctionOuterDef, 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::Tab(Indent)); // declare and zero-initialize the parameters and return value, if applicable if (!Parameters.HasParms()) return; FString EventStructName = GetEventStructParamsName(FunctionOuterDef, FunctionName); Output.Logf(TEXT("%s%s Parms;\r\n"), FCString::Tab(Indent + 1), *EventStructName ); // Declare a parameter struct for this event/delegate and assign the struct members using the values passed into the event/delegate call. for (FUnrealPropertyDefinitionInfo* PropDef : Parameters.Parms) { const FString PropertyName = PropDef->GetNameWithDeprecated(); if (PropDef->IsStaticArray()) { Output.Logf(TEXT("%sFMemory::Memcpy(Parms.%s,%s,sizeof(Parms.%s));\r\n"), FCString::Tab(Indent + 1), *PropertyName, *PropertyName, *PropertyName); } else { FString ValueAssignmentText = PropertyName; if (PropDef->IsBooleanOrBooleanStaticArray()) { ValueAssignmentText += TEXT(" ? true : false"); } Output.Logf(TEXT("%sParms.%s=%s;\r\n"), FCString::Tab(Indent + 1), *PropertyName, *ValueAssignmentText); } } } void WriteEventFunctionEpilogue(FOutputDevice& Output, int32 Indent, const FParmsAndReturnProperties& Parameters) { // Out parm copying. for (FUnrealPropertyDefinitionInfo* PropDef : Parameters.Parms) { if (PropDef->HasSpecificPropertyFlags(CPF_OutParm | CPF_ConstParm, CPF_OutParm)) { const FString PropertyName = PropDef->GetNameWithDeprecated(); if (PropDef->IsStaticArray()) { Output.Logf(TEXT("%sFMemory::Memcpy(&%s,&Parms.%s,sizeof(%s));\r\n"), FCString::Tab(Indent + 1), *PropertyName, *PropertyName, *PropertyName); } else { Output.Logf(TEXT("%s%s=Parms.%s;\r\n"), FCString::Tab(Indent + 1), *PropertyName, *PropertyName); } } } // Return value. if (Parameters.Return) { // Make sure uint32 -> bool is supported bool bBoolProperty = Parameters.Return->IsBooleanOrBooleanStaticArray(); Output.Logf(TEXT("%sreturn %sParms.%s;\r\n"), FCString::Tab(Indent + 1), bBoolProperty ? TEXT("!!") : TEXT(""), *Parameters.Return->GetName()); } Output.Logf(TEXT("%s}\r\n"), FCString::Tab(Indent)); } void FNativeClassHeaderGenerator::ExportDelegateDeclaration(FOutputDevice& Out, FReferenceGatherers& OutReferenceGatherers, const FUnrealSourceFile& SourceFile, FUnrealFunctionDefinitionInfo& FunctionDef) const { static const auto& DelegateStr = TEXT("delegate"); FFuncInfo FunctionData = FunctionDef.GetFunctionData(); // THIS IS A COPY check(FunctionDef.HasAnyFunctionFlags(FUNC_Delegate)); const bool bIsMulticastDelegate = FunctionDef.HasAnyFunctionFlags( FUNC_MulticastDelegate ); // Unmangle the function name const FString DelegateName = FunctionDef.GetName().LeftChop(HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX_LENGTH); // Add class name to beginning of function, to avoid collisions with other classes with the same delegate name in this scope check(FunctionData.MarshallAndCallName.StartsWith(DelegateStr)); FString ShortName = *FunctionData.MarshallAndCallName + UE_ARRAY_COUNT(DelegateStr) - 1; 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 ); FUHTStringBuilder DelegateOutput; DelegateOutput.Log(TEXT("static ")); // export the line that looks like: int32 Main(const FString& Parms) ExportNativeFunctionHeader(DelegateOutput, OutReferenceGatherers.ForwardDeclarations, FunctionDef, FunctionData, EExportFunctionType::Event, EExportFunctionHeaderStyle::Declaration, *ExtraParam, *GetAPIString()); // Only exporting function prototype DelegateOutput.Logf(TEXT(";\r\n")); ExportFunction(Out, OutReferenceGatherers, SourceFile, FunctionDef, false); } void FNativeClassHeaderGenerator::ExportDelegateDefinition(FOutputDevice& Out, FReferenceGatherers& OutReferenceGatherers, const FUnrealSourceFile& SourceFile, FUnrealFunctionDefinitionInfo& FunctionDef) const { const auto& DelegateStr = TEXT("delegate"); FFuncInfo FunctionData = FunctionDef.GetFunctionData(); // THIS IS A COPY check(FunctionDef.HasAnyFunctionFlags(FUNC_Delegate)); // Export parameters structs for all delegates. We'll need these to declare our delegate execution function. FUHTStringBuilder DelegateOutput; ExportEventParm(DelegateOutput, OutReferenceGatherers.ForwardDeclarations, FunctionDef, /*Indent=*/ 0, /*bOutputConstructor=*/ true, EExportingState::Normal); const bool bIsMulticastDelegate = FunctionDef.HasAnyFunctionFlags( FUNC_MulticastDelegate ); // Unmangle the function name const FString DelegateName = FunctionDef.GetName().LeftChop(HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX_LENGTH); // 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 check(FunctionData.MarshallAndCallName.StartsWith(DelegateStr)); FString ShortName = *FunctionData.MarshallAndCallName + UE_ARRAY_COUNT(DelegateStr) - 1; 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 ); DelegateOutput.Log(TEXT("static ")); // export the line that looks like: int32 Main(const FString& Parms) ExportNativeFunctionHeader(DelegateOutput, OutReferenceGatherers.ForwardDeclarations, FunctionDef, FunctionData, EExportFunctionType::Event, EExportFunctionHeaderStyle::Declaration, *ExtraParam, *GetAPIString()); FParmsAndReturnProperties Parameters = GetFunctionParmsAndReturn(FunctionDef); WriteEventFunctionPrologue(DelegateOutput, 0, Parameters, *FunctionDef.GetOuter(), *DelegateName); { const TCHAR* DelegateType = bIsMulticastDelegate ? TEXT( "ProcessMulticastDelegate" ) : TEXT( "ProcessDelegate" ); const TCHAR* DelegateArg = Parameters.HasParms() ? TEXT("&Parms") : TEXT("NULL"); DelegateOutput.Logf(TEXT("\t%s.%s(%s);\r\n"), *DelegateName, DelegateType, DelegateArg); } WriteEventFunctionEpilogue(DelegateOutput, 0, Parameters); FString MacroName = SourceFile.GetGeneratedMacroName(FunctionData.MacroLine, TEXT("_DELEGATE")); WriteMacro(Out, MacroName, DelegateOutput); } void FNativeClassHeaderGenerator::ExportEventParm(FUHTStringBuilder& Out, TSet& ForwardDeclarations, FUnrealFunctionDefinitionInfo& FunctionDef, int32 Indent, bool bOutputConstructor, EExportingState ExportingState) { if (!WillExportEventParms(FunctionDef)) { return; } FString FunctionName = FunctionDef.GetName(); if (FunctionDef.HasAnyFunctionFlags(FUNC_Delegate)) { FunctionName.LeftChopInline(HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX_LENGTH, false); } FString EventParmStructName = GetEventStructParamsName(*FunctionDef.GetOuter(), *FunctionName); Out.Logf(TEXT("%sstruct %s\r\n"), FCString::Tab(Indent), *EventParmStructName); Out.Logf(TEXT("%s{\r\n"), FCString::Tab(Indent)); for (TSharedRef PropDef : FunctionDef.GetProperties()) { FPropertyBase& PropertyBase = PropDef->GetPropertyBase(); if (!PropDef->HasAnyPropertyFlags(CPF_Parm)) { continue; } ForwardDeclarations.Add(PropDef->GetCPPTypeForwardDeclaration()); FUHTStringBuilder PropertyText; PropertyText.Log(FCString::Tab(Indent + 1)); bool bEmitConst = PropDef->HasAnyPropertyFlags(CPF_ConstParm) && PropertyBase.IsObjectRefOrObjectRefStaticArray(); //@TODO: UCREMOVAL: This is awful code duplication to avoid a double-const { //@TODO: bEmitConst will only be true if we have an object, so checking interface here doesn't do anything. // export 'const' for parameters const bool bIsConstParam = PropertyBase.IsInterfaceOrInterfaceStaticArray() && !PropDef->HasAllPropertyFlags(CPF_OutParm); //@TODO: This should be const once that flag exists const bool bIsOnConstClass = PropertyBase.IsObjectRefOrObjectRefStaticArray() && PropertyBase.ClassDef->HasAnyClassFlags(CLASS_Const); if (bIsConstParam || bIsOnConstClass) { bEmitConst = false; // ExportCppDeclaration will do it for us } } if (bEmitConst) { PropertyText.Logf(TEXT("const ")); } PropDef->ExportCppDeclaration(PropertyText, EExportedDeclaration::Local, PropDef->GetArrayDimensions()); ApplyAlternatePropertyExportText(*PropDef, PropertyText, ExportingState); PropertyText.Log(TEXT(";\r\n")); Out += *PropertyText; } // constructor must initialize the return property if it needs it FUnrealPropertyDefinitionInfo* PropDef = FunctionDef.GetReturn(); if (PropDef && bOutputConstructor) { const FPropertyBase& PropertyBase = PropDef->GetPropertyBase(); FUHTStringBuilder InitializationAr; bool bNeedsOutput = true; switch (PropertyBase.GetUHTPropertyType()) { case EUHTPropertyType::Struct: bNeedsOutput = PropDef->HasNoOpConstructor(); break; case EUHTPropertyType::Name: case EUHTPropertyType::Delegate: case EUHTPropertyType::MulticastDelegate: case EUHTPropertyType::String: case EUHTPropertyType::Text: case EUHTPropertyType::DynamicArray: case EUHTPropertyType::Map: case EUHTPropertyType::Set: case EUHTPropertyType::Interface: case EUHTPropertyType::FieldPath: bNeedsOutput = false; } if (bNeedsOutput) { check(!PropDef->IsStaticArray()); // can't return arrays Out.Logf(TEXT("\r\n%s/** Constructor, initializes return property only **/\r\n"), FCString::Tab(Indent + 1)); Out.Logf(TEXT("%s%s()\r\n"), FCString::Tab(Indent + 1), *EventParmStructName); Out.Logf(TEXT("%s%s %s(%s)\r\n"), FCString::Tab(Indent + 2), TEXT(":"), *PropDef->GetName(), *GetNullParameterValue(*PropDef, true)); Out.Logf(TEXT("%s{\r\n"), FCString::Tab(Indent + 1)); Out.Logf(TEXT("%s}\r\n"), FCString::Tab(Indent + 1)); } } Out.Logf(TEXT("%s};\r\n"), FCString::Tab(Indent)); } /** * 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(FUnrealPropertyDefinitionInfo& PropertyDef, bool bInitializer/*=false*/) { const FPropertyBase& PropertyBase = PropertyDef.GetPropertyBase(); switch (PropertyBase.GetUHTPropertyType()) { case EUHTPropertyType::Byte: case EUHTPropertyType::Int16: case EUHTPropertyType::Int: case EUHTPropertyType::Int64: case EUHTPropertyType::UInt16: case EUHTPropertyType::UInt32: case EUHTPropertyType::UInt64: case EUHTPropertyType::Float: case EUHTPropertyType::Double: case EUHTPropertyType::LargeWorldCoordinatesReal: return TEXT("0"); case EUHTPropertyType::Bool: case EUHTPropertyType::Bool8: case EUHTPropertyType::Bool16: case EUHTPropertyType::Bool32: case EUHTPropertyType::Bool64: return TEXT("false"); case EUHTPropertyType::ObjectReference: case EUHTPropertyType::ObjectPtrReference: case EUHTPropertyType::SoftObjectReference: case EUHTPropertyType::WeakObjectReference: case EUHTPropertyType::LazyObjectReference: return TEXT("NULL"); case EUHTPropertyType::Interface: return TEXT("NULL"); case EUHTPropertyType::Name: return TEXT("NAME_None"); case EUHTPropertyType::String: return TEXT("TEXT(\"\")"); case EUHTPropertyType::DynamicArray: case EUHTPropertyType::Map: case EUHTPropertyType::Set: case EUHTPropertyType::Delegate: case EUHTPropertyType::MulticastDelegate: { FString Type, ExtendedType; Type = PropertyDef.GetCPPType(&ExtendedType, CPPF_OptionalValue); return Type + ExtendedType + TEXT("()"); } case EUHTPropertyType::Struct: { bool bHasNoOpConstuctor = PropertyDef.HasNoOpConstructor(); if (bInitializer && bHasNoOpConstuctor) { return TEXT("ForceInit"); } FString Type, ExtendedType; Type = PropertyDef.GetCPPType(&ExtendedType, CPPF_OptionalValue); return Type + ExtendedType + (bHasNoOpConstuctor ? TEXT("(ForceInit)") : TEXT("()")); } case EUHTPropertyType::Text: return TEXT("FText::GetEmpty()"); case EUHTPropertyType::Enum: { if (PropertyBase.EnumDef->GetCppForm() != UEnum::ECppForm::EnumClass) { return TEXT("0"); } return FString::Printf(TEXT("(%s)0"), *PropertyDef.GetCPPType()); } case EUHTPropertyType::FieldPath: return TEXT("nullptr"); default: UE_LOG(LogCompile, Fatal, TEXT("GetNullParameterValue - Unhandled property type '%s': %s"), *PropertyDef.GetEngineClassName(), *PropertyDef.GetPathName()); return TEXT(""); } } FString FNativeClassHeaderGenerator::GetFunctionReturnString(FUnrealFunctionDefinitionInfo& FunctionDef, FReferenceGatherers& OutReferenceGatherers) { FString Result; if (FUnrealPropertyDefinitionInfo* ReturnDef = FunctionDef.GetReturn()) { FString ExtendedReturnType; OutReferenceGatherers.ForwardDeclarations.Add(ReturnDef->GetCPPTypeForwardDeclaration()); FString ReturnType = ReturnDef->GetCPPType(&ExtendedReturnType, CPPF_ArgumentOrReturnValue); FUHTStringBuilder ReplacementText; ReplacementText += MoveTemp(ReturnType); ApplyAlternatePropertyExportText(*ReturnDef, ReplacementText, EExportingState::Normal); Result = MoveTemp(ReplacementText) + MoveTemp(ExtendedReturnType); } else { Result = TEXT("void"); } return Result; } void FNativeClassHeaderGenerator::CheckRPCFunctions(FReferenceGatherers& OutReferenceGatherers, FUnrealFunctionDefinitionInfo& FunctionDef, const FString& ClassName, const FFindDelcarationResults& Implementation, const FFindDelcarationResults& Validation, const FUnrealSourceFile& SourceFile) const { bool bHasImplementation = Implementation.WasFound(); bool bHasValidate = Validation.WasFound(); const FFuncInfo& FunctionData = FunctionDef.GetFunctionData(); FString FunctionReturnType = GetFunctionReturnString(FunctionDef, OutReferenceGatherers); const TCHAR* ConstModifier = (FunctionDef.HasAllFunctionFlags(FUNC_Const) ? TEXT("const ") : TEXT(" ")); const bool bIsNative = FunctionDef.HasAllFunctionFlags(FUNC_Native); const bool bIsNet = FunctionDef.HasAllFunctionFlags(FUNC_Net); const bool bIsNetValidate = FunctionDef.HasAllFunctionFlags(FUNC_NetValidate); const bool bIsNetResponse = FunctionDef.HasAllFunctionFlags(FUNC_NetResponse); const bool bIsBlueprintEvent = FunctionDef.HasAllFunctionFlags(FUNC_BlueprintEvent); bool bNeedsImplementation = (bIsNet && !bIsNetResponse) || bIsBlueprintEvent || bIsNative; bool bNeedsValidate = (bIsNative || bIsNet) && !bIsNetResponse && bIsNetValidate; check(bNeedsImplementation || bNeedsValidate); FString ParameterString = GetFunctionParameterString(FunctionDef, OutReferenceGatherers); const FString& Filename = SourceFile.GetFilename(); const FString& FileContent = SourceFile.GetContent(); // // Get string with function specifiers, listing why we need _Implementation or _Validate functions. // TArray> FunctionSpecifiers; if (bIsNative) { FunctionSpecifiers.Add(TEXT("Native")); } if (bIsNet) { FunctionSpecifiers.Add(TEXT("Net")); } if (bIsBlueprintEvent) { FunctionSpecifiers.Add(TEXT("BlueprintEvent")); } if (bIsNetValidate) { FunctionSpecifiers.Add(TEXT("NetValidate")); } check(FunctionSpecifiers.Num() > 0); // // Coin static_assert message // FUHTStringBuilder AssertMessage; AssertMessage.Logf(TEXT("Function %s was marked as %s"), *(FunctionDef.GetName()), FunctionSpecifiers[0]); for (int32 i = 1; i < FunctionSpecifiers.Num(); ++i) { AssertMessage.Logf(TEXT(", %s"), FunctionSpecifiers[i]); } AssertMessage.Logf(TEXT(".")); // // Check if functions are missing. // if (bNeedsImplementation && !bHasImplementation) { FString FunctionDecl = FString::Printf(TEXT("virtual %s %s::%s(%s) %s"), *FunctionReturnType, *ClassName, *FunctionData.CppImplName, *ParameterString, *ConstModifier); FunctionDef.Throwf(TEXT("%s Declare function %s"), *AssertMessage, *FunctionDecl); } if (bNeedsValidate && !bHasValidate) { FString FunctionDecl = FString::Printf(TEXT("virtual bool %s::%s(%s) %s"), *ClassName, *FunctionData.CppValidationImplName, *ParameterString, *ConstModifier); FunctionDef.Throwf(TEXT("%s Declare function %s"), *AssertMessage, *FunctionDecl); } // // If all needed functions are declared, check if they have virtual specifiers. // if (bNeedsImplementation && bHasImplementation && !Implementation.IsVirtual()) { FString FunctionDecl = FString::Printf(TEXT("%s %s::%s(%s) %s"), *FunctionReturnType, *ClassName, *FunctionData.CppImplName, *ParameterString, *ConstModifier); FunctionDef.Throwf(TEXT("Declared function %sis not marked as virtual."), *FunctionDecl); } if (bNeedsValidate && bHasValidate && !Validation.IsVirtual()) { FString FunctionDecl = FString::Printf(TEXT("bool %s::%s(%s) %s"), *ClassName, *FunctionData.CppValidationImplName, *ParameterString, *ConstModifier); FunctionDef.Throwf(TEXT("Declared function %sis not marked as virtual."), *FunctionDecl); } } void FNativeClassHeaderGenerator::ExportNativeFunctionHeader( FOutputDevice& Out, TSet& ForwardDeclarations, FUnrealFunctionDefinitionInfo& FunctionDef, const FFuncInfo& FunctionData, EExportFunctionType::Type FunctionType, EExportFunctionHeaderStyle::Type FunctionHeaderStyle, const TCHAR* ExtraParam, const TCHAR* APIString ) { const bool bIsDelegate = FunctionDef.HasAnyFunctionFlags( FUNC_Delegate ); const bool bIsInterface = !bIsDelegate && FunctionDef.GetOwnerClass()->HasAnyClassFlags(CLASS_Interface); const bool bIsK2Override = FunctionDef.HasAnyFunctionFlags( FUNC_BlueprintEvent ); if (!bIsDelegate) { Out.Log(TEXT("\t")); } 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 (FunctionType != EExportFunctionType::Event && !FunctionDef.GetOwnerClass()->HasAnyClassFlags(CLASS_RequiredAPI) && (FunctionData.FunctionExportFlags & FUNCEXPORT_RequiredAPI)) { Out.Log(APIString); } if(FunctionType == EExportFunctionType::Interface) { Out.Log(TEXT("static ")); } else if (bIsK2Override) { Out.Log(TEXT("virtual ")); } // if the owning class is an interface class else if ( bIsInterface ) { Out.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 && !FunctionDef.HasAnyFunctionFlags(FUNC_Static) && !(FunctionData.FunctionExportFlags & FUNCEXPORT_Final) ) { Out.Log(TEXT("virtual ")); } else if( FunctionData.FunctionExportFlags & FUNCEXPORT_Inline ) { Out.Log(TEXT("inline ")); } } FUnrealPropertyDefinitionInfo* ReturnPropertyDef = FunctionDef.GetReturn(); if (ReturnPropertyDef != nullptr) { if (ReturnPropertyDef->HasAnyPropertyFlags(EPropertyFlags::CPF_ConstParm)) { Out.Log(TEXT("const ")); } FString ExtendedReturnType; FString ReturnType = ReturnPropertyDef->GetCPPType(&ExtendedReturnType, (FunctionHeaderStyle == EExportFunctionHeaderStyle::Definition && (FunctionType != EExportFunctionType::Interface) ? CPPF_Implementation : 0) | CPPF_ArgumentOrReturnValue); ForwardDeclarations.Add(ReturnPropertyDef->GetCPPTypeForwardDeclaration()); FUHTStringBuilder ReplacementText; ReplacementText += ReturnType; ApplyAlternatePropertyExportText(*ReturnPropertyDef, ReplacementText, EExportingState::Normal); Out.Logf(TEXT("%s%s"), *ReplacementText, *ExtendedReturnType); } else { Out.Log( TEXT("void") ); } FString FunctionName; if (FunctionHeaderStyle == EExportFunctionHeaderStyle::Definition) { FunctionName = FString::Printf(TEXT("%s::"), *UHTCastChecked(FunctionDef.GetOuter()).GetAlternateNameCPP(bIsInterface || FunctionType == EExportFunctionType::Interface)); } if (FunctionType == EExportFunctionType::Interface) { FunctionName += FString::Printf(TEXT("Execute_%s"), *FunctionDef.GetName()); } else if (FunctionType == EExportFunctionType::Event) { FunctionName += FunctionData.MarshallAndCallName; } else { FunctionName += FunctionData.CppImplName; } Out.Logf(TEXT(" %s("), *FunctionName); int32 ParmCount=0; // Emit extra parameter if we have one if( ExtraParam ) { Out.Log(ExtraParam); ++ParmCount; } for (TSharedRef PropertyDef : FunctionDef.GetProperties()) { if (!PropertyDef->HasSpecificPropertyFlags(CPF_Parm | CPF_ReturnParm, CPF_Parm)) { continue; } ForwardDeclarations.Add(PropertyDef->GetCPPTypeForwardDeclaration()); if( ParmCount++ ) { Out.Log(TEXT(", ")); } FUHTStringBuilder PropertyText; PropertyDef->ExportCppDeclaration( PropertyText, EExportedDeclaration::Parameter, PropertyDef->GetArrayDimensions() ); ApplyAlternatePropertyExportText(*PropertyDef, PropertyText, EExportingState::Normal); Out.Log(*PropertyText); } Out.Log( TEXT(")") ); if (FunctionType != EExportFunctionType::Interface) { if (!bIsDelegate && FunctionDef.HasAllFunctionFlags(FUNC_Const)) { Out.Log( TEXT(" const") ); } if (bIsInterface && FunctionHeaderStyle == EExportFunctionHeaderStyle::Declaration) { // all methods in interface classes are pure virtuals if (bIsK2Override) { // For BlueprintNativeEvent methods we emit a stub implementation. This allows Blueprints that implement the interface class to be nativized. FString ReturnValue; if (ReturnPropertyDef != nullptr) { if (ReturnPropertyDef->IsByteEnumOrByteEnumStaticArray()) { ReturnValue = FString::Printf(TEXT(" return TEnumAsByte<%s>(%s); "), *ReturnPropertyDef->GetPropertyBase().AsEnum()->GetCppType(), *GetNullParameterValue(*ReturnPropertyDef, false)); } else { ReturnValue = FString::Printf(TEXT(" return %s; "), *GetNullParameterValue(*ReturnPropertyDef, false)); } } Out.Logf(TEXT(" {%s}"), *ReturnValue); } else { Out.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 * @param DeprecationWarningOutputDevice Device to output deprecation warnings for _Validate and _Implementation functions. */ void FNativeClassHeaderGenerator::ExportFunctionThunk(FUHTStringBuilder& RPCWrappers, FReferenceGatherers& OutReferenceGatherers, FUnrealFunctionDefinitionInfo& FunctionDef, const TArray& ParameterDefs, FUnrealPropertyDefinitionInfo* ReturnDef) const { const FFuncInfo& FunctionData = FunctionDef.GetFunctionData(); // export the GET macro for this parameter FString ParameterList; for (int32 ParameterIndex = 0; ParameterIndex < ParameterDefs.Num(); ParameterIndex++) { FUnrealPropertyDefinitionInfo* ParamDef = ParameterDefs[ParameterIndex]; const FPropertyBase& PropertyBase = ParamDef->GetPropertyBase(); OutReferenceGatherers.ForwardDeclarations.Add(ParamDef->GetCPPTypeForwardDeclaration()); FString EvalBaseText = TEXT("P_GET_"); // e.g. P_GET_STR FString EvalModifierText; // e.g. _REF FString EvalParameterText; // e.g. (UObject*,NULL) FString TypeText; if (ParamDef->IsStaticArray()) { EvalBaseText += TEXT("ARRAY"); TypeText = ParamDef->GetCPPType(); } else { EvalBaseText += ParamDef->GetCPPMacroType(TypeText); if (PropertyBase.ArrayType == EArrayType::Dynamic && PropertyBase.Type == CPT_Interface) { FString InterfaceTypeText; ParamDef->GetValuePropDef().GetCPPMacroType(InterfaceTypeText); TypeText += FString::Printf(TEXT("<%s>"), *InterfaceTypeText); } } bool bPassAsNoPtr = ParamDef->HasAllPropertyFlags(CPF_UObjectWrapper | CPF_OutParm) && ParamDef->IsClassRefOrClassRefStaticArray(); if (bPassAsNoPtr) { TypeText = ParamDef->GetCPPType(); } FUHTStringBuilder ReplacementText; ReplacementText += TypeText; ApplyAlternatePropertyExportText(*ParamDef, ReplacementText, EExportingState::Normal); TypeText = ReplacementText; FString DefaultValueText; FString ParamPrefix = TEXT("Z_Param_"); // if this property is an out parm, add the REF tag if (ParamDef->HasAnyPropertyFlags(CPF_OutParm)) { if (!bPassAsNoPtr) { EvalModifierText += TEXT("_REF"); } else { // Parameters passed as TSubclassOf& shouldn't have asterisk added. EvalModifierText += TEXT("_REF_NO_PTR"); } 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 + ParamDef->GetName(); EvalParameterText = FString::Printf(TEXT("(%s%s%s)"), *TypeText, *ParamName, *DefaultValueText); RPCWrappers.Logf(TEXT("\t\t%s%s%s;" LINE_TERMINATOR_ANSI), *EvalBaseText, *EvalModifierText, *EvalParameterText); // add this property to the parameter list string if (ParameterList.Len()) { ParameterList += TCHAR(','); } if (PropertyBase.Type == CPT_Delegate && PropertyBase.IsPrimitiveOrPrimitiveStaticArray()) { // For delegates, add an explicit conversion to the specific type of delegate before passing it along const FString FunctionName = PropertyBase.FunctionDef->GetName().LeftChop(HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX_LENGTH); ParamName = FString::Printf(TEXT("F%s(%s)"), *FunctionName, *ParamName); } if (PropertyBase.Type == CPT_MulticastDelegate && PropertyBase.IsPrimitiveOrPrimitiveStaticArray()) { // For delegates, add an explicit conversion to the specific type of delegate before passing it along const FString FunctionName = PropertyBase.FunctionDef->GetName().LeftChop(HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX_LENGTH); ParamName = FString::Printf(TEXT("F%s(%s)"), *FunctionName, *ParamName); } if (FUnrealEnumDefinitionInfo* EnumDef = PropertyBase.IsPrimitiveOrPrimitiveStaticArray() ? PropertyBase.AsEnum() : nullptr) { // For enums, add an explicit conversion if (!ParamDef->HasAnyPropertyFlags(CPF_OutParm)) { ParamName = FString::Printf(TEXT("%s(%s)"), *EnumDef->GetCppType(), *ParamName); } else { if (EnumDef->GetCppForm() == UEnum::ECppForm::EnumClass) { // If we're an enum class don't require the wrapper ParamName = FString::Printf(TEXT("(%s&)(%s)"), *EnumDef->GetCppType(), *ParamName); } else { ParamName = FString::Printf(TEXT("(TEnumAsByte<%s>&)(%s)"), *EnumDef->GetCppType(), *ParamName); } } } ParameterList += ParamName; } RPCWrappers += TEXT("\t\tP_FINISH;" LINE_TERMINATOR_ANSI); RPCWrappers += TEXT("\t\tP_NATIVE_BEGIN;" LINE_TERMINATOR_ANSI); FUnrealClassDefinitionInfo* OwnerClassDef = FunctionDef.GetOwnerClass(); check(OwnerClassDef); FString ClassName = OwnerClassDef->GetName(); bool bHasImplementation = FindDeclaration(*OwnerClassDef, FunctionData.CppImplName).WasFound(); bool bHasValidate = FindDeclaration(*OwnerClassDef, FunctionData.CppValidationImplName).WasFound(); bool bShouldEnableImplementationDeprecation = // Enable deprecation warnings only if GENERATED_BODY is used inside class or interface (not GENERATED_UCLASS_BODY etc.) OwnerClassDef->HasGeneratedBody() // and implementation function is called, but not the one declared by user && (FunctionData.CppImplName != FunctionDef.GetName() && !bHasImplementation); bool bShouldEnableValidateDeprecation = // Enable deprecation warnings only if GENERATED_BODY is used inside class or interface (not GENERATED_UCLASS_BODY etc.) OwnerClassDef->HasGeneratedBody() // and validation function is called && FunctionDef.HasAnyFunctionFlags(FUNC_NetValidate) && !bHasValidate; //Emit warning here if necessary FUHTStringBuilder FunctionDeclaration; ExportNativeFunctionHeader(FunctionDeclaration, OutReferenceGatherers.ForwardDeclarations, FunctionDef, FunctionData, EExportFunctionType::Function, EExportFunctionHeaderStyle::Declaration, nullptr, *GetAPIString()); // Call the validate function if there is one if (!(FunctionData.FunctionExportFlags & FUNCEXPORT_CppStatic) && FunctionDef.HasAnyFunctionFlags(FUNC_NetValidate)) { RPCWrappers.Logf(TEXT("\t\tif (!P_THIS->%s(%s))" LINE_TERMINATOR_ANSI), *FunctionData.CppValidationImplName, *ParameterList); RPCWrappers.Logf(TEXT("\t\t{" LINE_TERMINATOR_ANSI)); RPCWrappers.Logf(TEXT("\t\t\tRPC_ValidateFailed(TEXT(\"%s\"));" LINE_TERMINATOR_ANSI), *FunctionData.CppValidationImplName); RPCWrappers.Logf(TEXT("\t\t\treturn;" LINE_TERMINATOR_ANSI)); // If we got here, the validation function check failed RPCWrappers.Logf(TEXT("\t\t}" LINE_TERMINATOR_ANSI)); } // write out the return value RPCWrappers.Log(TEXT("\t\t")); if (ReturnDef) { OutReferenceGatherers.ForwardDeclarations.Add(ReturnDef->GetCPPTypeForwardDeclaration()); FUHTStringBuilder ReplacementText; FString ReturnExtendedType; ReplacementText += ReturnDef->GetCPPType(&ReturnExtendedType); ApplyAlternatePropertyExportText(*ReturnDef, ReplacementText, EExportingState::Normal); FString ReturnType = ReplacementText; if (ReturnDef->HasAnyPropertyFlags(CPF_ConstParm) && ReturnDef->IsObjectRefOrObjectRefStaticArray()) { ReturnType = TEXT("const ") + ReturnType; } RPCWrappers.Logf(TEXT("*(%s%s*)" PREPROCESSOR_TO_STRING(RESULT_PARAM) "="), *ReturnType, *ReturnExtendedType); } // export the call to the C++ version if (FunctionData.FunctionExportFlags & FUNCEXPORT_CppStatic) { RPCWrappers.Logf(TEXT("%s::%s(%s);" LINE_TERMINATOR_ANSI), *FunctionDef.GetOwnerClass()->GetAlternateNameCPP(), *FunctionData.CppImplName, *ParameterList); } else { RPCWrappers.Logf(TEXT("P_THIS->%s(%s);" LINE_TERMINATOR_ANSI), *FunctionData.CppImplName, *ParameterList); } RPCWrappers += TEXT("\t\tP_NATIVE_END;" LINE_TERMINATOR_ANSI); } FString FNativeClassHeaderGenerator::GetFunctionParameterString(FUnrealFunctionDefinitionInfo& FunctionDef, FReferenceGatherers& OutReferenceGatherers) { FString ParameterList; FUHTStringBuilder PropertyText; for (TSharedRef PropertyDef : FunctionDef.GetProperties()) { OutReferenceGatherers.ForwardDeclarations.Add(PropertyDef->GetCPPTypeForwardDeclaration()); if (!PropertyDef->HasSpecificPropertyFlags(CPF_Parm | CPF_ReturnParm, CPF_Parm)) { break; } if (ParameterList.Len()) { ParameterList += TEXT(", "); } PropertyDef->ExportCppDeclaration(PropertyText, EExportedDeclaration::Parameter, PropertyDef->GetArrayDimensions(), 0, true); ApplyAlternatePropertyExportText(*PropertyDef, PropertyText, EExportingState::Normal); ParameterList += PropertyText; PropertyText.Reset(); } return ParameterList; } struct FNativeFunctionStringBuilder { FUHTStringBuilder RPCWrappers; FUHTStringBuilder RPCImplementations; FUHTStringBuilder AutogeneratedBlueprintFunctionDeclarations; FUHTStringBuilder AutogeneratedBlueprintFunctionDeclarationsOnlyNotDeclared; FUHTStringBuilder AutogeneratedStaticData; FUHTStringBuilder AutogeneratedStaticDataFuncs; }; void FNativeClassHeaderGenerator::ExportNativeFunctions(FOutputDevice& OutGeneratedHeaderText, FOutputDevice& OutGeneratedCPPText, FOutputDevice& OutMacroCalls, FOutputDevice& OutNoPureDeclsMacroCalls, FReferenceGatherers& OutReferenceGatherers, const FUnrealSourceFile& SourceFile, FUnrealClassDefinitionInfo& ClassDef) const { FNativeFunctionStringBuilder RuntimeStringBuilders; FNativeFunctionStringBuilder EditorStringBuilders; const FString ClassCPPName = ClassDef.GetAlternateNameCPP(ClassDef.HasAnyClassFlags(CLASS_Interface)); // gather static class data TArray SparseClassDataTypes; ClassDef.GetSparseClassDataTypes(SparseClassDataTypes); FString FullClassName = ClassDef.GetNameWithPrefix(); for (const FString& SparseClassDataString : SparseClassDataTypes) { RuntimeStringBuilders.AutogeneratedStaticData.Logf(TEXT("F%s* Get%s()\r\n"), *SparseClassDataString, *SparseClassDataString); RuntimeStringBuilders.AutogeneratedStaticData += TEXT("{\r\n"); RuntimeStringBuilders.AutogeneratedStaticData.Logf(TEXT("\treturn (F%s*)(GetClass()->GetOrCreateSparseClassData());\r\n"), *SparseClassDataString); RuntimeStringBuilders.AutogeneratedStaticData += TEXT("}\r\n"); RuntimeStringBuilders.AutogeneratedStaticData.Logf(TEXT("F%s* Get%s() const\r\n"), *SparseClassDataString, *SparseClassDataString); RuntimeStringBuilders.AutogeneratedStaticData += TEXT("{\r\n"); RuntimeStringBuilders.AutogeneratedStaticData.Logf(TEXT("\treturn (F%s*)(GetClass()->GetOrCreateSparseClassData());\r\n"), *SparseClassDataString); RuntimeStringBuilders.AutogeneratedStaticData += TEXT("}\r\n"); RuntimeStringBuilders.AutogeneratedStaticData.Logf(TEXT("const F%s* Get%s(EGetSparseClassDataMethod GetMethod) const\r\n"), *SparseClassDataString, *SparseClassDataString); RuntimeStringBuilders.AutogeneratedStaticData += TEXT("{\r\n"); RuntimeStringBuilders.AutogeneratedStaticData.Logf(TEXT("\treturn (const F%s*)(GetClass()->GetSparseClassData(GetMethod));\r\n"), *SparseClassDataString); RuntimeStringBuilders.AutogeneratedStaticData += TEXT("}\r\n"); FUnrealScriptStructDefinitionInfo* SparseClassDataStructDef = GTypeDefinitionInfoMap.FindByName(*SparseClassDataString); while (SparseClassDataStructDef != nullptr) { for (TSharedRef PropertyDef : SparseClassDataStructDef->GetProperties()) { FString ReturnExtendedType; FString VarType = PropertyDef->GetCPPType(&ReturnExtendedType, EPropertyExportCPPFlags::CPPF_ArgumentOrReturnValue | EPropertyExportCPPFlags::CPPF_Implementation); if (!ReturnExtendedType.IsEmpty()) { VarType.Append(ReturnExtendedType); } FString VarName = PropertyDef->GetName(); FString CleanVarName = VarName; if (PropertyDef->IsBooleanOrBooleanStaticArray() && VarName.StartsWith(TEXT("b"), ESearchCase::CaseSensitive)) { CleanVarName = VarName.RightChop(1); } if (!PropertyDef->HasMetaData(NAME_NoGetter)) { if (PropertyDef->HasMetaData(NAME_GetByRef)) { RuntimeStringBuilders.AutogeneratedStaticDataFuncs.Logf(TEXT("const %s& Get%s()\r\n"), *VarType, *CleanVarName); } else { RuntimeStringBuilders.AutogeneratedStaticDataFuncs.Logf(TEXT("%s Get%s()\r\n"), *VarType, *CleanVarName); } RuntimeStringBuilders.AutogeneratedStaticDataFuncs.Logf(TEXT("{\r\n")); RuntimeStringBuilders.AutogeneratedStaticDataFuncs.Logf(TEXT("\treturn Get%s()->%s;\r\n"), *SparseClassDataString, *VarName); RuntimeStringBuilders.AutogeneratedStaticDataFuncs.Logf(TEXT("}\r\n")); if (PropertyDef->HasMetaData(NAME_GetByRef)) { RuntimeStringBuilders.AutogeneratedStaticDataFuncs.Logf(TEXT("const %s& Get%s() const\r\n"), *VarType, *CleanVarName); } else { RuntimeStringBuilders.AutogeneratedStaticDataFuncs.Logf(TEXT("%s Get%s() const\r\n"), *VarType, *CleanVarName); } RuntimeStringBuilders.AutogeneratedStaticDataFuncs.Logf(TEXT("{\r\n")); RuntimeStringBuilders.AutogeneratedStaticDataFuncs.Logf(TEXT("\treturn Get%s()->%s;\r\n"), *SparseClassDataString, *VarName); RuntimeStringBuilders.AutogeneratedStaticDataFuncs.Logf(TEXT("}\r\n")); } } SparseClassDataStructDef = UHTCast(SparseClassDataStructDef->GetSuperStructInfo().Struct); } } TArray> Functions; Algo::Copy(ClassDef.GetFunctions(), Functions); Algo::Reverse(Functions); // export the C++ stubs for (TSharedRef FunctionDef : Functions) { const FFuncInfo& FunctionData = FunctionDef->GetFunctionData(); if (!FunctionDef->HasAnyFunctionFlags(FUNC_Native)) { continue; } const bool bEditorOnlyFunc = FunctionDef->HasAnyFunctionFlags(FUNC_EditorOnly); FNativeFunctionStringBuilder& FuncStringBuilders = bEditorOnlyFunc ? EditorStringBuilders : RuntimeStringBuilders; // Custom thunks don't get any C++ stub function generated if (FunctionData.FunctionExportFlags & FUNCEXPORT_CustomThunk) { continue; } // Should we emit these to RPC wrappers or just ignore them? const bool bWillBeProgrammerTyped = FunctionData.CppImplName == FunctionDef->GetName(); if (!bWillBeProgrammerTyped) { FString FunctionName = FunctionDef->GetName(); FFindDelcarationResults Implementation = FindDeclaration(ClassDef, FunctionData.CppImplName); FFindDelcarationResults Validation = FindDeclaration(ClassDef, FunctionData.CppValidationImplName); //Emit warning here if necessary FUHTStringBuilder FunctionDeclaration; ExportNativeFunctionHeader(FunctionDeclaration, OutReferenceGatherers.ForwardDeclarations, *FunctionDef, FunctionData, EExportFunctionType::Function, EExportFunctionHeaderStyle::Declaration, nullptr, *GetAPIString()); FunctionDeclaration.Log(TEXT(";\r\n")); // Declare validation function if needed if (FunctionDef->HasAnyFunctionFlags(FUNC_NetValidate)) { FString ParameterList = GetFunctionParameterString(*FunctionDef, OutReferenceGatherers); const TCHAR* Virtual = (!FunctionDef->HasAnyFunctionFlags(FUNC_Static) && !(FunctionData.FunctionExportFlags & FUNCEXPORT_Final)) ? TEXT("virtual") : TEXT(""); FStringOutputDevice ValidDecl; ValidDecl.Logf(TEXT("\t%s bool %s(%s);\r\n"), Virtual, *FunctionData.CppValidationImplName, *ParameterList); FuncStringBuilders.AutogeneratedBlueprintFunctionDeclarations.Log(*ValidDecl); if (!Validation.WasFound()) { FuncStringBuilders.AutogeneratedBlueprintFunctionDeclarationsOnlyNotDeclared.Log(*ValidDecl); } } FuncStringBuilders.AutogeneratedBlueprintFunctionDeclarations.Log(*FunctionDeclaration); if (!Implementation.WasFound() && FunctionData.CppImplName != FunctionName) { FuncStringBuilders.AutogeneratedBlueprintFunctionDeclarationsOnlyNotDeclared.Log(*FunctionDeclaration); } // Versions that skip function autodeclaration throw an error when a function is missing. if (ClassDef.HasGeneratedBody() && (ClassDef.GetGeneratedCodeVersion() > EGeneratedCodeVersion::V1)) { CheckRPCFunctions(OutReferenceGatherers, *FunctionDef, ClassCPPName, Implementation, Validation, SourceFile); } } FuncStringBuilders.RPCWrappers.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 (!ShouldExportFunction(*FunctionDef)) { continue; } // export the script wrappers FuncStringBuilders.RPCWrappers.Logf(TEXT("\tDECLARE_FUNCTION(%s);"), *FunctionData.UnMarshallAndCallName); FuncStringBuilders.RPCImplementations.Logf(TEXT("\tDEFINE_FUNCTION(%s::%s)"), *ClassCPPName, *FunctionData.UnMarshallAndCallName); FuncStringBuilders.RPCImplementations += TEXT(LINE_TERMINATOR_ANSI "\t{" LINE_TERMINATOR_ANSI); FParmsAndReturnProperties Parameters = GetFunctionParmsAndReturn(*FunctionDef); ExportFunctionThunk(FuncStringBuilders.RPCImplementations, OutReferenceGatherers, *FunctionDef, Parameters.Parms, Parameters.Return); FuncStringBuilders.RPCImplementations += TEXT("\t}" LINE_TERMINATOR_ANSI); } // static class data { FString MacroName = SourceFile.GetGeneratedMacroName(ClassDef.GetGeneratedBodyLine(), TEXT("_SPARSE_DATA")); WriteMacro(OutGeneratedHeaderText, MacroName, RuntimeStringBuilders.AutogeneratedStaticData + RuntimeStringBuilders.AutogeneratedStaticDataFuncs); OutMacroCalls.Logf(TEXT("\t%s\r\n"), *MacroName); OutNoPureDeclsMacroCalls.Logf(TEXT("\t%s\r\n"), *MacroName); } // Write runtime wrappers { FString MacroName = SourceFile.GetGeneratedMacroName(ClassDef.GetGeneratedBodyLine(), TEXT("_RPC_WRAPPERS")); // WriteMacro has an assumption about what will be at the end of this block that is no longer true due to splitting the // definition and implementation, so add on a line terminator to satisfy it if (RuntimeStringBuilders.RPCWrappers.Len() > 0) { RuntimeStringBuilders.RPCWrappers += LINE_TERMINATOR; } WriteMacro(OutGeneratedHeaderText, MacroName, RuntimeStringBuilders.AutogeneratedBlueprintFunctionDeclarations + RuntimeStringBuilders.RPCWrappers); OutMacroCalls.Logf(TEXT("\t%s\r\n"), *MacroName); // Put static checks before RPCWrappers to get proper messages from static asserts before compiler errors. FString NoPureDeclsMacroName = SourceFile.GetGeneratedMacroName(ClassDef.GetGeneratedBodyLine(), TEXT("_RPC_WRAPPERS_NO_PURE_DECLS")); if (ClassDef.GetGeneratedCodeVersion() > EGeneratedCodeVersion::V1) { WriteMacro(OutGeneratedHeaderText, NoPureDeclsMacroName, RuntimeStringBuilders.RPCWrappers); } else { WriteMacro(OutGeneratedHeaderText, NoPureDeclsMacroName, RuntimeStringBuilders.AutogeneratedBlueprintFunctionDeclarationsOnlyNotDeclared + RuntimeStringBuilders.RPCWrappers); } OutNoPureDeclsMacroCalls.Logf(TEXT("\t%s\r\n"), *NoPureDeclsMacroName); OutGeneratedCPPText.Log(RuntimeStringBuilders.RPCImplementations); } // Write editor only RPC wrappers if they exist if (EditorStringBuilders.RPCWrappers.Len() > 0) { OutGeneratedHeaderText.Log( BeginEditorOnlyGuard ); FString MacroName = SourceFile.GetGeneratedMacroName(ClassDef.GetGeneratedBodyLine(), TEXT("_EDITOR_ONLY_RPC_WRAPPERS")); // WriteMacro has an assumption about what will be at the end of this block that is no longer true due to splitting the // definition and implementation, so add on a line terminator to satisfy it if (EditorStringBuilders.RPCWrappers.Len() > 0) { EditorStringBuilders.RPCWrappers += LINE_TERMINATOR; } WriteMacro(OutGeneratedHeaderText, MacroName, EditorStringBuilders.AutogeneratedBlueprintFunctionDeclarations + EditorStringBuilders.RPCWrappers); OutMacroCalls.Logf(TEXT("\t%s\r\n"), *MacroName); // Put static checks before RPCWrappers to get proper messages from static asserts before compiler errors. FString NoPureDeclsMacroName = SourceFile.GetGeneratedMacroName(ClassDef.GetGeneratedBodyLine(), TEXT("_EDITOR_ONLY_RPC_WRAPPERS_NO_PURE_DECLS")); if (ClassDef.GetGeneratedCodeVersion() > EGeneratedCodeVersion::V1) { WriteMacro(OutGeneratedHeaderText, NoPureDeclsMacroName, EditorStringBuilders.RPCWrappers); } else { WriteMacro(OutGeneratedHeaderText, NoPureDeclsMacroName, EditorStringBuilders.AutogeneratedBlueprintFunctionDeclarationsOnlyNotDeclared + EditorStringBuilders.RPCWrappers); } // write out an else preprocessor block for when not compiling for the editor. The generated macros should be empty then since the functions are compiled out { OutGeneratedHeaderText.Log(TEXT("#else\r\n")); WriteMacro(OutGeneratedHeaderText, MacroName, TEXT("")); WriteMacro(OutGeneratedHeaderText, NoPureDeclsMacroName, TEXT("")); OutGeneratedHeaderText.Log(EndEditorOnlyGuard); } OutNoPureDeclsMacroCalls.Logf(TEXT("\t%s\r\n"), *NoPureDeclsMacroName); OutGeneratedCPPText.Log(BeginEditorOnlyGuard); OutGeneratedCPPText.Log(EditorStringBuilders.RPCImplementations); OutGeneratedCPPText.Log(EndEditorOnlyGuard); } } /** * Exports the methods which trigger UnrealScript events and delegates. * * @param CallbackFunctions the functions to export */ void FNativeClassHeaderGenerator::ExportCallbackFunctions( FOutputDevice& OutGeneratedHeaderText, FOutputDevice& OutCpp, TSet& OutFwdDecls, const TArray& CallbackFunctions, const TCHAR* CallbackWrappersMacroName, EExportCallbackType ExportCallbackType, const TCHAR* APIString ) { FUHTStringBuilder RPCWrappers; FMacroBlockEmitter OutCppEditorOnly(OutCpp, TEXT("WITH_EDITOR")); for (FUnrealFunctionDefinitionInfo* FunctionDef : CallbackFunctions) { // Never expecting to export delegate functions this way check(!FunctionDef->HasAnyFunctionFlags(FUNC_Delegate)); const FFuncInfo& FunctionData = FunctionDef->GetFunctionData(); FString FunctionName = FunctionDef->GetName(); FUnrealClassDefinitionInfo& ClassDef = UHTCastChecked(FunctionDef->GetOuter()); const FString ClassName = ClassDef.GetAlternateNameCPP(); if (FunctionDef->HasAnyFunctionFlags(FUNC_NetResponse)) { // Net response functions don't go into the VM continue; } const bool bIsEditorOnly = FunctionDef->HasAnyFunctionFlags(FUNC_EditorOnly); OutCppEditorOnly(bIsEditorOnly); const bool bWillBeProgrammerTyped = FunctionName == 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(RPCWrappers, OutFwdDecls, *FunctionDef, FunctionData, EExportFunctionType::Event, EExportFunctionHeaderStyle::Declaration, nullptr, APIString); RPCWrappers.Log(TEXT(";\r\n")); RPCWrappers.Log(TEXT("\r\n")); } FString FunctionNameName; if (ExportCallbackType != EExportCallbackType::Interface) { FunctionNameName = FString::Printf(TEXT("NAME_%s_%s"), *ClassName, *FunctionName); OutCpp.Logf(TEXT("\tstatic FName %s = FName(TEXT(\"%s\"));" LINE_TERMINATOR_ANSI), *FunctionNameName, *FunctionName); } // Emit the thunk implementation ExportNativeFunctionHeader(OutCpp, OutFwdDecls, *FunctionDef, FunctionData, EExportFunctionType::Event, EExportFunctionHeaderStyle::Definition, nullptr, APIString); FParmsAndReturnProperties Parameters = GetFunctionParmsAndReturn(*FunctionDef); if (ExportCallbackType != EExportCallbackType::Interface) { WriteEventFunctionPrologue(OutCpp, /*Indent=*/ 1, Parameters, ClassDef, *FunctionName); { // Cast away const just in case, because ProcessEvent isn't const OutCpp.Logf( TEXT("\t\t%sProcessEvent(FindFunctionChecked(%s),%s);\r\n"), (FunctionDef->HasAllFunctionFlags(FUNC_Const)) ? *FString::Printf(TEXT("const_cast<%s*>(this)->"), *ClassName) : TEXT(""), *FunctionNameName, Parameters.HasParms() ? TEXT("&Parms") : TEXT("NULL") ); } WriteEventFunctionEpilogue(OutCpp, /*Indent=*/ 1, Parameters); } else { OutCpp.Log(LINE_TERMINATOR); OutCpp.Log(TEXT("\t{" LINE_TERMINATOR_ANSI)); // assert if this is ever called directly OutCpp.Logf(TEXT("\t\tcheck(0 && \"Do not directly call Event functions in Interfaces. Call Execute_%s instead.\");" LINE_TERMINATOR_ANSI), *FunctionName); // satisfy compiler if it's expecting a return value if (Parameters.Return) { FString EventParmStructName = GetEventStructParamsName(ClassDef, *FunctionName); OutCpp.Logf(TEXT("\t\t%s Parms;" LINE_TERMINATOR_ANSI), *EventParmStructName); OutCpp.Log(TEXT("\t\treturn Parms.ReturnValue;" LINE_TERMINATOR_ANSI)); } OutCpp.Log(TEXT("\t}" LINE_TERMINATOR_ANSI)); } } WriteMacro(OutGeneratedHeaderText, CallbackWrappersMacroName, RPCWrappers); } /** * 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(FUnrealPropertyDefinitionInfo& PropertyDef, FUHTStringBuilder& PropertyText, EExportingState ExportingState) { const FPropertyBase& PropertyBase = PropertyDef.GetPropertyBase(); if (FUnrealEnumDefinitionInfo* EnumDef = PropertyBase.AsEnum()) { return; } if (ExportingState == EExportingState::TypeEraseDelegates) { if (PropertyBase.Type == CPT_Delegate || PropertyBase.Type == CPT_MulticastDelegate) { FString Original = PropertyDef.GetCPPType(); const TCHAR* PlaceholderOfSameSizeAndAlignemnt; if (PropertyBase.Type == CPT_Delegate) { PlaceholderOfSameSizeAndAlignemnt = TEXT("FScriptDelegate"); } else { PlaceholderOfSameSizeAndAlignemnt = TEXT("FMulticastScriptDelegate"); } PropertyText.ReplaceInline(*Original, PlaceholderOfSameSizeAndAlignemnt, ESearchCase::CaseSensitive); } } } bool FNativeClassHeaderGenerator::WriteSource(const FManifestModule& Module, FGeneratedFileInfo& FileInfo, const FString& InBodyText, FUnrealSourceFile* InSourceFile, const TSet& InCrossModuleReferences) { // Collect the includes if this is from a source file TArray RelativeIncludes; if (InSourceFile) { FString ModuleRelativeFilename = InSourceFile->GetFilename(); ConvertToBuildIncludePath(Module, ModuleRelativeFilename); RelativeIncludes.Add(MoveTemp(ModuleRelativeFilename)); bool bAddedStructuredArchiveFromArchiveHeader = false; bool bAddedArchiveUObjectFromStructuredArchiveHeader = false; for (const TSharedRef& TypeDef : InSourceFile->GetDefinedClasses()) { FUnrealClassDefinitionInfo& ClassDef = TypeDef->AsClassChecked(); FUnrealClassDefinitionInfo* ClassWithin = ClassDef.GetClassWithin(); if (ClassWithin && ClassWithin->HasSource() && !ClassWithin->GetUnrealSourceFile().IsNoExportTypes()) { FString Header = GetBuildPath(ClassWithin->GetUnrealSourceFile()); RelativeIncludes.AddUnique(MoveTemp(Header)); } if (!bAddedStructuredArchiveFromArchiveHeader && ClassDef.GetArchiveType() == ESerializerArchiveType::StructuredArchiveRecord) { RelativeIncludes.AddUnique(TEXT("Serialization/StructuredArchive.h")); bAddedStructuredArchiveFromArchiveHeader = true; } if (!bAddedArchiveUObjectFromStructuredArchiveHeader && ClassDef.GetArchiveType() == ESerializerArchiveType::Archive) { RelativeIncludes.AddUnique(TEXT("Serialization/ArchiveUObjectFromStructuredArchive.h")); bAddedArchiveUObjectFromStructuredArchiveHeader = true; } } } FUHTStringBuilder FileText; FileText.Log(HeaderCopyright); FileText.Log(RequiredCPPIncludes); for (const FString& RelativeInclude : RelativeIncludes) { FileText.Logf(TEXT("#include \"%s\"\r\n"), *RelativeInclude); } FileText.Log(DisableDeprecationWarnings); FString CleanFilename = FPaths::GetCleanFilename(FileInfo.GetFilename()); CleanFilename.ReplaceInline(TEXT(".gen.cpp"), TEXT(""), ESearchCase::CaseSensitive); CleanFilename.ReplaceInline(TEXT("."), TEXT("_"), ESearchCase::CaseSensitive); FileText.Logf(TEXT("void EmptyLinkFunctionForGeneratedCode%s() {}" LINE_TERMINATOR_ANSI), *CleanFilename); if (InCrossModuleReferences.Num() > 0) { TArray Sorted; Sorted.Reserve(InCrossModuleReferences.Num()); for (const FString& Ref : InCrossModuleReferences) { Sorted.Add(&Ref); } Sorted.Sort(); FileText.Logf(TEXT("// Cross Module References\r\n")); for (const FString* Ref : Sorted) { FileText.Log(**Ref); } FileText.Logf(TEXT("// End Cross Module References\r\n")); } FileText.Log(*InBodyText); FileText.Log(EnableDeprecationWarnings); return SaveHeaderIfChanged(FileInfo, MoveTemp(FileText)); } // Constructor. FNativeClassHeaderGenerator::FNativeClassHeaderGenerator( FUnrealPackageDefinitionInfo& InPackageDef) : PackageDef(InPackageDef) {} const FString& FNativeClassHeaderGenerator::GetAPIString() const { return PackageDef.GetAPI(); } bool FNativeClassHeaderGenerator::LoadSourceFile(FGeneratedCPP& GeneratedCPP) { const FManifestModule& Module = GeneratedCPP.PackageDef.GetModule(); FUnrealSourceFile& SourceFile = GeneratedCPP.SourceFile; if (!SourceFile.ShouldExport()) { return false; } FString ModuleRelativeFilename = SourceFile.GetFilename(); ConvertToBuildIncludePath(Module, ModuleRelativeFilename); FString StrippedName = FPaths::GetBaseFilename(MoveTemp(ModuleRelativeFilename)); FString HeaderPath = (Module.GeneratedIncludeDirectory / StrippedName) + TEXT(".generated.h"); FString GeneratedSourceFilename = (Module.GeneratedIncludeDirectory / StrippedName) + TEXT(".gen.cpp"); #if UHT_ENABLE_CONCURRENT_CODE_GENERATION GeneratedCPP.Header.StartLoad(MoveTemp(HeaderPath)); GeneratedCPP.Source.StartLoad(MoveTemp(GeneratedSourceFilename)); #else GeneratedCPP.Header.Load(MoveTemp(HeaderPath)); GeneratedCPP.Source.Load(MoveTemp(GeneratedSourceFilename)); #endif return true; } void FNativeClassHeaderGenerator::GenerateSourceFile(FGeneratedCPP& GeneratedCPP) { FResults::Try([&GeneratedCPP]() { FUnrealPackageDefinitionInfo& PackageDefLcl = GeneratedCPP.PackageDef; const FManifestModule& Module = PackageDefLcl.GetModule(); const FNativeClassHeaderGenerator Generator(PackageDefLcl); FUnrealSourceFile& SourceFile = GeneratedCPP.SourceFile; FUHTStringBuilder& GeneratedFunctionDeclarations = GeneratedCPP.GeneratedFunctionDeclarations; FUHTStringBuilder& GeneratedHeaderText = GeneratedCPP.Header.GetGeneratedBody(); FUHTStringBuilder& GeneratedCPPText = GeneratedCPP.Source.GetGeneratedBody(); FScopedDurationTimer SourceTimer(SourceFile.GetTime(ESourceFileTime::Generate)); FReferenceGatherers ReferenceGatherers(&GeneratedCPP.CrossModuleReferences, GeneratedCPP.ForwardDeclarations); TArray Types; SourceFile.GetScope()->GatherTypes(Types); // Make sure that all the types have a definition range. for (FUnrealFieldDefinitionInfo* TypeDef : Types) { TypeDef->ValidateDefinitionRange(); } // Sort by the end of the definition range. Since classes can contain delegates, we can't use the definition line // since that will place the class ahead of the delegate. Types.StableSort([](const FUnrealFieldDefinitionInfo& Lhs, const FUnrealFieldDefinitionInfo& Rhs) { return Lhs.GetDefinitionRange().End < Rhs.GetDefinitionRange().End; } ); TArray Singletons; Singletons.Reserve(Types.Num()); TArray EnumRegs; EnumRegs.Reserve(Types.Num()); TArray ScriptStructRegs; ScriptStructRegs.Reserve(Types.Num()); TArray ClassRegs; ClassRegs.Reserve(Types.Num()); const FString FileDefineName = SourceFile.GetFileDefineName(); const FString& StrippedFilename = SourceFile.GetStrippedFilename(); GeneratedHeaderText.Logf( TEXT("#ifdef %s" LINE_TERMINATOR_ANSI "#error \"%s.generated.h already included, missing '#pragma once' in %s.h\"" LINE_TERMINATOR_ANSI "#endif" LINE_TERMINATOR_ANSI "#define %s" LINE_TERMINATOR_ANSI LINE_TERMINATOR_ANSI), *FileDefineName, *StrippedFilename, *StrippedFilename, *FileDefineName); for (FUnrealFieldDefinitionInfo* FieldDef : Types) { if (FUnrealEnumDefinitionInfo* EnumDef = UHTCast(FieldDef)) { // Is this ever not the case? if (EnumDef->GetOuter()->GetObject()->IsA(UPackage::StaticClass())) { GeneratedFunctionDeclarations.Log(EnumDef->GetExternDecl(true)); Generator.ExportGeneratedEnumInitCode(GeneratedCPPText, ReferenceGatherers, SourceFile, *EnumDef); EnumRegs.Add(EnumDef); } } else if (FUnrealScriptStructDefinitionInfo* ScriptStructDef = UHTCast(FieldDef)) { if (ScriptStructDef->HasAnyStructFlags(STRUCT_NoExport)) { Singletons.Add(ScriptStructDef); } GeneratedFunctionDeclarations.Log(ScriptStructDef->GetExternDecl(true)); Generator.ExportGeneratedStructBodyMacros(GeneratedHeaderText, GeneratedCPPText, ReferenceGatherers, SourceFile, *ScriptStructDef); if (ScriptStructDef->HasAnyStructFlags(STRUCT_Native)) { ScriptStructRegs.Add(ScriptStructDef); } } else if (FUnrealFunctionDefinitionInfo* FunctionDef = UHTCast(FieldDef)) { Singletons.Add(FunctionDef); GeneratedFunctionDeclarations.Log(FunctionDef->GetExternDecl(true)); Generator.ExportDelegateDeclaration(GeneratedCPPText, ReferenceGatherers, SourceFile, *FunctionDef); Generator.ExportDelegateDefinition(GeneratedHeaderText, ReferenceGatherers, SourceFile, *FunctionDef); } else if (FUnrealClassDefinitionInfo* ClassDef = UHTCast(FieldDef)) { if (!ClassDef->HasAnyClassFlags(CLASS_Intrinsic)) { Generator.ExportClassFromSourceFileInner(GeneratedHeaderText, GeneratedCPPText, GeneratedFunctionDeclarations, ReferenceGatherers, *ClassDef, SourceFile, GeneratedCPP.ExportFlags); ClassRegs.Add(ClassDef); } } } GeneratedHeaderText.Log(TEXT("#undef CURRENT_FILE_ID\r\n")); GeneratedHeaderText.Logf(TEXT("#define CURRENT_FILE_ID %s\r\n\r\n\r\n"), *SourceFile.GetFileId()); for (FUnrealFieldDefinitionInfo* FieldDef : Types) { if (FUnrealEnumDefinitionInfo* EnumDef = UHTCast(FieldDef)) { Generator.ExportEnum(GeneratedHeaderText, *EnumDef); } } // Generate the single registration method if (!EnumRegs.IsEmpty() || !ScriptStructRegs.IsEmpty() || !ClassRegs.IsEmpty()) { static const TCHAR* Prefix = TEXT("Z_CompiledInDeferFile_"); FString StaticsName = FString::Printf(TEXT("%s%s_Statics"), Prefix, *SourceFile.GetFileId()); GeneratedCPPText.Logf(TEXT("\tstruct %s\r\n"), *StaticsName); GeneratedCPPText.Log(TEXT("\t{\r\n")); size_t EditorOnlyEnumCount = 0; for (FUnrealEnumDefinitionInfo* EnumDef : EnumRegs) { if (EnumDef->IsEditorOnly()) { ++EditorOnlyEnumCount; } } bool bAllEnumsEditorOnly = !EnumRegs.IsEmpty() && EditorOnlyEnumCount == EnumRegs.Num(); if (!EnumRegs.IsEmpty()) { if (bAllEnumsEditorOnly) { GeneratedCPPText.Log(TEXT("#if WITH_EDITORONLY_DATA\r\n")); } GeneratedCPPText.Log(TEXT("\t\tstatic const FEnumRegisterCompiledInInfo EnumInfo[];\r\n")); if (bAllEnumsEditorOnly) { GeneratedCPPText.Log(TEXT("#endif\r\n")); } } if (!ScriptStructRegs.IsEmpty()) { GeneratedCPPText.Log(TEXT("\t\tstatic const FStructRegisterCompiledInInfo ScriptStructInfo[];\r\n")); } if (!ClassRegs.IsEmpty()) { GeneratedCPPText.Log(TEXT("\t\tstatic const FClassRegisterCompiledInInfo ClassInfo[];\r\n")); } GeneratedCPPText.Log(TEXT("\t};\r\n")); int32 CombinedHash = -1; if (!EnumRegs.IsEmpty()) { if (bAllEnumsEditorOnly) { GeneratedCPPText.Log(TEXT("#if WITH_EDITORONLY_DATA\r\n")); } GeneratedCPPText.Logf(TEXT("\tconst FEnumRegisterCompiledInInfo %s::EnumInfo[] = {\r\n"), *StaticsName); for (FUnrealEnumDefinitionInfo* EnumDef : EnumRegs) { if (!bAllEnumsEditorOnly && EnumDef->IsEditorOnly()) { GeneratedCPPText.Log(TEXT("#if WITH_EDITORONLY_DATA\r\n")); } const FString EnumNameCpp = EnumDef->GetName(); GeneratedCPPText.Logf(TEXT("\t\t{ %s_StaticEnum, TEXT(\"%s\"), &Z_Registration_Info_UEnum_%s, CONSTRUCT_RELOAD_VERSION_INFO(FEnumReloadVersionInfo, %uU) },\r\n"), *EnumNameCpp, *EnumNameCpp, *EnumNameCpp, EnumDef->GetHash(*EnumDef)); if (!bAllEnumsEditorOnly && EnumDef->IsEditorOnly()) { GeneratedCPPText.Log(TEXT("#endif\r\n")); } CombinedHash = HashCombine(CombinedHash, EnumDef->GetHash(*EnumDef)); } GeneratedCPPText.Logf(TEXT("\t};\r\n")); if (bAllEnumsEditorOnly) { GeneratedCPPText.Log(TEXT("#endif\r\n")); } } if (!ScriptStructRegs.IsEmpty()) { GeneratedCPPText.Logf(TEXT("\tconst FStructRegisterCompiledInInfo %s::ScriptStructInfo[] = {\r\n"), *StaticsName); for (FUnrealScriptStructDefinitionInfo* ScriptStructDef : ScriptStructRegs) { const FString StructNameCPP = ScriptStructDef->GetAlternateNameCPP(); GeneratedCPPText.Logf(TEXT("\t\t{ %s::StaticStruct, Z_Construct_UScriptStruct_%s_Statics::NewStructOps, TEXT(\"%s\"), &Z_Registration_Info_UScriptStruct_%s, CONSTRUCT_RELOAD_VERSION_INFO(FStructReloadVersionInfo, sizeof(%s), %uU) },\r\n"), *StructNameCPP, *StructNameCPP, *ScriptStructDef->GetName(), *ScriptStructDef->GetName(), *StructNameCPP, ScriptStructDef->GetHash(*ScriptStructDef)); CombinedHash = HashCombine(CombinedHash, ScriptStructDef->GetHash(*ScriptStructDef)); } GeneratedCPPText.Logf(TEXT("\t};\r\n")); } if (!ClassRegs.IsEmpty()) { GeneratedCPPText.Logf(TEXT("\tconst FClassRegisterCompiledInInfo %s::ClassInfo[] = {\r\n"), *StaticsName); for (FUnrealClassDefinitionInfo* ClassDef : ClassRegs) { const FString ClassNameCPP = ClassDef->GetAlternateNameCPP(); GeneratedCPPText.Logf(TEXT("\t\t{ Z_Construct_UClass_%s, %s::StaticClass, TEXT(\"%s\"), &Z_Registration_Info_UClass_%s, CONSTRUCT_RELOAD_VERSION_INFO(FClassReloadVersionInfo, sizeof(%s), %uU) },\r\n"), *ClassNameCPP, *ClassNameCPP, *ClassNameCPP, *ClassNameCPP, *ClassNameCPP, ClassDef->GetHash(*ClassDef)); CombinedHash = HashCombine(CombinedHash, ClassDef->GetHash(*ClassDef)); } GeneratedCPPText.Logf(TEXT("\t};\r\n")); } auto FormatArray = [&StaticsName](bool bIsEmpty, bool bAllEditorOnlyData, const FString& Text) { if (bAllEditorOnlyData) { return FString::Printf(TEXT("IF_WITH_EDITORONLY_DATA(%s::%s, nullptr), IF_WITH_EDITORONLY_DATA(UE_ARRAY_COUNT(%s::%s), 0)"), *StaticsName, *Text, *StaticsName, *Text); } else if (!bIsEmpty) { return FString::Printf(TEXT("%s::%s, UE_ARRAY_COUNT(%s::%s)"), *StaticsName, *Text, *StaticsName, *Text); } else { return FString(TEXT("nullptr, 0")); } }; // We add the hash to the name to generate a unique version of the name so that LiveCoding's default "only invoke static constructor once" // is avoided when elements change. GeneratedCPPText.Logf(TEXT("\tstatic FRegisterCompiledInInfo %s%s_%u(TEXT(\"%s\"),\r\n\t\t%s,\r\n\t\t%s,\r\n\t\t%s);\r\n"), Prefix, *SourceFile.GetFileId(), CombinedHash, *PackageDefLcl.GetName(), *FormatArray(ClassRegs.IsEmpty(), false, TEXT("ClassInfo")), *FormatArray(ScriptStructRegs.IsEmpty(), false, TEXT("ScriptStructInfo")), *FormatArray(EnumRegs.IsEmpty(), bAllEnumsEditorOnly, TEXT("EnumInfo")) ); } if (Singletons.Num()) { SourceFile.GetSingletons().Append(MoveTemp(Singletons)); } // Sort the forward declarations to make them more stable TArray Lines; GeneratedFunctionDeclarations.ParseIntoArrayLines(Lines); TSet Unique; for (const FString& Line : Lines) { Unique.Add(Line); } Lines.Empty(); for (const FString& Line : Unique) { Lines.Add(Line); } Lines.Sort(); GeneratedFunctionDeclarations.Empty(); for (const FString& Line : Lines) { GeneratedFunctionDeclarations.Logf(TEXT("%s\r\n"), *Line); } #if UHT_ENABLE_EXTRA_HASH_OUTPUT GeneratedCPPText.Logf(TEXT("#if 0\r\n")); GeneratedCPPText.Log(GeneratedFunctionDeclarations); GeneratedCPPText.Logf(TEXT("#endif\r\n")); #endif } ); } void FNativeClassHeaderGenerator::WriteSourceFile(FGeneratedCPP& GeneratedCPP) { FResults::Try([&GeneratedCPP]() { FUnrealPackageDefinitionInfo& PackageDefLcl = GeneratedCPP.PackageDef; const FManifestModule& Module = PackageDefLcl.GetModule(); FUnrealSourceFile& SourceFile = GeneratedCPP.SourceFile; FUHTStringBuilder& GeneratedHeaderText = GeneratedCPP.Header.GetGeneratedBody(); FUHTStringBuilder& GeneratedCPPText = GeneratedCPP.Source.GetGeneratedBody(); FScopedDurationTimer SourceTimer(SourceFile.GetTime(ESourceFileTime::Generate)); GeneratedCPP.Source.GenerateBodyHash(); TSet AdditionalHeaders; if (EnumHasAnyFlags(GeneratedCPP.ExportFlags, EExportClassOutFlags::NeedsPushModelHeaders)) { AdditionalHeaders.Add(FString(TEXT("Net/Core/PushModel/PushModelMacros.h"))); } bool bHasChanged = WriteHeader(GeneratedCPP.Header, GeneratedHeaderText, AdditionalHeaders, GeneratedCPP.ForwardDeclarations); WriteSource(Module, GeneratedCPP.Source, GeneratedCPPText, &SourceFile, GeneratedCPP.CrossModuleReferences); SourceFile.SetGeneratedFilename(MoveTemp(GeneratedCPP.Header.GetFilename())); SourceFile.SetHasChanged(bHasChanged); } ); } void FNativeClassHeaderGenerator::GenerateSourceFiles( TArray& GeneratedCPPs ) { #if UHT_ENABLE_CONCURRENT_CODE_GENERATION TSet Includes; Includes.Reserve(GeneratedCPPs.Num()); FGraphEventArray TempTasks; TempTasks.Reserve(3); for (FGeneratedCPP& GeneratedCPP : GeneratedCPPs) { if (!LoadSourceFile(GeneratedCPP)) { continue; } TempTasks.Reset(); GeneratedCPP.Header.AddLoadTaskRef(TempTasks); GeneratedCPP.Source.AddLoadTaskRef(TempTasks); auto GenerateSource = [&GeneratedCPP]() { GenerateSourceFile(GeneratedCPP); }; auto WriteGenerated = [&GeneratedCPP]() { WriteSourceFile(GeneratedCPP); }; Includes.Reset(); for (FHeaderProvider& Header : GeneratedCPP.SourceFile.GetIncludes()) { if (FUnrealSourceFile* Include = Header.Resolve(GeneratedCPP.SourceFile)) { Includes.Add(Include); } } // Our generation must wait on all of our includes generation to complete TempTasks.Reset(); for (FUnrealSourceFile* Include : Includes) { FGeneratedCPP& IncludeCPP = GeneratedCPPs[Include->GetOrderedIndex()]; IncludeCPP.AddGenerateTaskRef(TempTasks); } GeneratedCPP.GenerateTaskRef = FFunctionGraphTask::CreateAndDispatchWhenReady(MoveTemp(GenerateSource), TStatId(), &TempTasks); // Our compare and save must wait on generation and loading of the current header and source TempTasks.Reset(); TempTasks.Add(GeneratedCPP.GenerateTaskRef); GeneratedCPP.Header.AddLoadTaskRef(TempTasks); GeneratedCPP.Source.AddLoadTaskRef(TempTasks); GeneratedCPP.ExportTaskRef = FFunctionGraphTask::CreateAndDispatchWhenReady(MoveTemp(WriteGenerated), TStatId(), &TempTasks); } // When this task fires, all of the exports for this package have completed FGraphEventArray ExportSourceTasks; ExportSourceTasks.Reserve(GeneratedCPPs.Num()); for (FGeneratedCPP& GeneratedCPP : GeneratedCPPs) { GeneratedCPP.AddExportTaskRef(ExportSourceTasks); } // This is strange, but flushing the log can take a long time. Without an explicit flush, we were getting a long stall in the UE_LOG message // during the detection of script plugins. By doing it here, the flush should easily complete during code generation. GLog->FlushThreadedLogs(); FTaskGraphInterface::Get().WaitUntilTasksComplete(ExportSourceTasks); #else for (FGeneratedCPP& GeneratedCPP : GeneratedCPPs) { if (!LoadSourceFile(GeneratedCPP)) { continue; } GenerateSourceFile(GeneratedCPP); WriteSourceFile(GeneratedCPP); } #endif FResults::WaitForErrorTasks(); } void FNativeClassHeaderGenerator::Generate( FUnrealPackageDefinitionInfo& PackageDef, TArray& GeneratedCPPs ) { UPackage* Package = PackageDef.GetPackage(); FString PackageName = FPackageName::GetShortName(Package); const FManifestModule& Module = PackageDef.GetModule(); const bool bWriteClassesH = PackageDef.GetWriteClassesH(); const bool bAllowSaveExportedHeaders = Module.SaveExportedHeaders; FGraphEventArray TempTasks; TempTasks.Reserve(3); // Create a list of all exported files TArray Exported; Exported.Reserve(GeneratedCPPs.Num()); for (FGeneratedCPP& GeneratedCPP : GeneratedCPPs) { if (&GeneratedCPP.PackageDef == &PackageDef && GeneratedCPP.SourceFile.ShouldExport()) { Exported.Emplace(&GeneratedCPP); } } // Create a sorted list of the exported source files so that the generated code is consistent TArray ExportedSorted(Exported); ExportedSorted.Sort([](const FGeneratedCPP& Lhs, const FGeneratedCPP& Rhs) { return Lhs.SourceFile.GetFilename() < Rhs.SourceFile.GetFilename(); }); // Generate the package tasks FGraphEventArray PackageTasks; PackageTasks.Reserve(2); // If we are generated the classes H file, start the preload process for the H file TArray GeneratedPackageFileInfo; GeneratedPackageFileInfo.Reserve(2); if (bWriteClassesH) { // Start loading the original header file for comparison FGeneratedFileInfo& GeneratedFileInfo = GeneratedPackageFileInfo[GeneratedPackageFileInfo.Add(FGeneratedFileInfo(bAllowSaveExportedHeaders))]; FString ClassesHeaderPath = Module.GeneratedIncludeDirectory / (PackageName + TEXT("Classes.h")); GeneratedFileInfo.StartLoad(MoveTemp(ClassesHeaderPath)); auto ClasssesH = [&PackageDef, &GeneratedFileInfo, &ExportedSorted]() { FResults::Try([&PackageDef, &GeneratedFileInfo, &ExportedSorted]() { const FManifestModule& Module = PackageDef.GetModule(); // Write the classes and enums header prefixes. FUHTStringBuilder ClassesHText; ClassesHText.Log(HeaderCopyright); ClassesHText.Log(TEXT("#pragma once\r\n")); ClassesHText.Log(TEXT("\r\n")); ClassesHText.Log(TEXT("\r\n")); // Fill with the rest source files from this package. TSet PublicHeaderGroupIncludes; for (FGeneratedCPP* GeneratedCPP : ExportedSorted) { if (GeneratedCPP->SourceFile.IsPublic()) { PublicHeaderGroupIncludes.Add(&GeneratedCPP->SourceFile); } } for (TSharedRef& SourceFile : PackageDef.GetAllSourceFiles()) { if (SourceFile->IsPublic()) { PublicHeaderGroupIncludes.Add(&*SourceFile); } } // Make the public header list stable regardless of the order the files are parsed. TArray BuildPaths; BuildPaths.Reserve(PublicHeaderGroupIncludes.Num()); for (FUnrealSourceFile* SourceFile : PublicHeaderGroupIncludes) { BuildPaths.Emplace(GetBuildPath(*SourceFile)); } BuildPaths.Sort(); for (const FString& BuildPath : BuildPaths) { ClassesHText.Logf(TEXT("#include \"%s\"" LINE_TERMINATOR_ANSI), *BuildPath); } ClassesHText.Log(LINE_TERMINATOR); // Save the classes header if it has changed. SaveHeaderIfChanged(GeneratedFileInfo, MoveTemp(ClassesHText)); }); }; TempTasks.Reset(); GeneratedFileInfo.AddLoadTaskRef(TempTasks); FGraphEventRef GenerateTask = FFunctionGraphTask::CreateAndDispatchWhenReady(MoveTemp(ClasssesH), TStatId(), &TempTasks); PackageTasks.Add(GenerateTask); } // 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 int32 InitGenIndex = -1; { FGeneratedFileInfo& GeneratedFileInfo = GeneratedPackageFileInfo[InitGenIndex = GeneratedPackageFileInfo.Add(FGeneratedFileInfo(bAllowSaveExportedHeaders))]; FString GeneratedSourceFilename = Module.GeneratedIncludeDirectory / FString::Printf(TEXT("%s.init.gen.cpp"), *PackageName); GeneratedFileInfo.StartLoad(MoveTemp(GeneratedSourceFilename)); auto Functions = [&PackageDef, &GeneratedFileInfo, &ExportedSorted]() { FResults::Try([&PackageDef, &GeneratedFileInfo, &ExportedSorted]() { const FManifestModule& Module = PackageDef.GetModule(); // Export an include line for each header FUHTStringBuilder GeneratedFunctionDeclarations; for (FGeneratedCPP* GeneratedCPP : ExportedSorted) { GeneratedFunctionDeclarations.Log(GeneratedCPP->GeneratedFunctionDeclarations); } if (GeneratedFunctionDeclarations.Len()) { FNativeClassHeaderGenerator Generator(PackageDef); uint32 CombinedHash = 0; for (FGeneratedCPP* GeneratedCPP : ExportedSorted) { uint32 SourceHash = GeneratedCPP->Source.GetGeneratedBodyHash(); if (CombinedHash == 0) { // Don't combine in the first case because it keeps GUID backwards compatibility CombinedHash = SourceHash; } else { CombinedHash = HashCombine(SourceHash, CombinedHash); } } Generator.ExportGeneratedPackageInitCode(GeneratedFileInfo.GetGeneratedBody(), *GeneratedFunctionDeclarations, CombinedHash); WriteSource(Module, GeneratedFileInfo, GeneratedFileInfo.GetGeneratedBody(), nullptr, TSet()); } }); }; TempTasks.Reset(); GeneratedFileInfo.AddLoadTaskRef(TempTasks); FGraphEventRef GenerateTask = FFunctionGraphTask::CreateAndDispatchWhenReady(MoveTemp(Functions), TStatId(), &TempTasks); PackageTasks.Add(GenerateTask); } FTaskGraphInterface::Get().WaitUntilTasksComplete(PackageTasks); // Collect all of the paths and save tasks int32 MaxCount = (Exported.Num() * 2) + GeneratedPackageFileInfo.Num(); TSet PackageHeaderPaths; TArray TempHeaderPaths; FGraphEventArray SaveTasks; PackageHeaderPaths.Reserve(MaxCount); TempHeaderPaths.Reserve(MaxCount); SaveTasks.Reserve(MaxCount); for (FGeneratedCPP* GeneratedCPP : Exported) { if (bAllowSaveExportedHeaders) { GeneratedCPP->Header.AddPackageFilename(PackageHeaderPaths); GeneratedCPP->Source.AddPackageFilename(PackageHeaderPaths); } GeneratedCPP->Header.AddTempFilename(TempHeaderPaths); GeneratedCPP->Source.AddTempFilename(TempHeaderPaths); GeneratedCPP->Header.AddSaveTaskRef(SaveTasks); GeneratedCPP->Source.AddSaveTaskRef(SaveTasks); } for (FGeneratedFileInfo& GeneratedFileInfo : GeneratedPackageFileInfo) { if (bAllowSaveExportedHeaders) { GeneratedFileInfo.AddPackageFilename(PackageHeaderPaths); } GeneratedFileInfo.AddTempFilename(TempHeaderPaths); GeneratedFileInfo.AddSaveTaskRef(SaveTasks); } // Export all changed headers from their temp files to the .h files ExportUpdatedHeaders(MoveTemp(PackageName), MoveTemp(TempHeaderPaths), SaveTasks); // Delete stale *.generated.h files if (bAllowSaveExportedHeaders) { DeleteUnusedGeneratedHeaders(MoveTemp(PackageHeaderPaths)); } } TArray GSourceWildcards = { TEXT("*.generated.cpp"), TEXT("*.generated.*.cpp"), TEXT("*.gen.cpp"), TEXT("*.gen.*.cpp") }; TArray GHeaderWildcards = { TEXT("*.generated.h") }; bool MatchesWildcards(const TArray& Wildcards, const TCHAR* Filename) { for (const FWildcardString& Wildcard : Wildcards) { if (Wildcard.IsMatch(Filename)) { return true; } } return false; } void FNativeClassHeaderGenerator::DeleteUnusedGeneratedHeaders(TSet&& PackageHeaderPathSet) { auto DeleteUnusedGeneratedHeadersTask = [PackageHeaderPathSet = MoveTemp(PackageHeaderPathSet)]() { TSet AllIntermediateFolders; for (const FString& PackageHeader : PackageHeaderPathSet) { FString IntermediatePath = FPaths::GetPath(PackageHeader); if (AllIntermediateFolders.Contains(IntermediatePath)) { continue; } class FFileVisitor : public IPlatformFile::FDirectoryVisitor { public: const FString& Directory; const TSet& PackageHeaderPathSet; FFileVisitor(const FString& InDirectory, const TSet& InPackageHeaderPathSet) : Directory(InDirectory) , PackageHeaderPathSet(InPackageHeaderPathSet) { } virtual bool Visit(const TCHAR* FileNameOrDirectory, bool bIsDirectory) { if (!bIsDirectory) { FString Fullpath(FileNameOrDirectory); if (!PackageHeaderPathSet.Contains(Fullpath)) { FString Filename = FPaths::GetCleanFilename(Fullpath); if (MatchesWildcards(GSourceWildcards, *Filename)) { IFileManager::Get().Delete(*Fullpath); } else if (MatchesWildcards(GHeaderWildcards, *Filename)) { // Is this intrinsic test valid anymore? FString BaseFilename = FPaths::GetBaseFilename(Filename); const int32 GeneratedIndex = BaseFilename.Find(TEXT(".generated"), ESearchCase::IgnoreCase, ESearchDir::FromEnd); const FString ClassName = MoveTemp(BaseFilename).Mid(0, GeneratedIndex); UClass* IntrinsicClass = FEngineAPI::FindObject(ANY_PACKAGE, *ClassName); if (!IntrinsicClass || !IntrinsicClass->HasAnyClassFlags(CLASS_Intrinsic)) { IFileManager::Get().Delete(*Fullpath); } } } } return true; } }; FFileVisitor Visitor(IntermediatePath, PackageHeaderPathSet); IFileManager::Get().IterateDirectory(*IntermediatePath, Visitor); AllIntermediateFolders.Add(MoveTemp(IntermediatePath)); } }; GAsyncFileTasks.Add(FFunctionGraphTask::CreateAndDispatchWhenReady(MoveTemp(DeleteUnusedGeneratedHeadersTask), TStatId())); } FCriticalSection TestCommandLineCS; bool FNativeClassHeaderGenerator::SaveHeaderIfChanged(FGeneratedFileInfo& FileInfo, FString&& InNewHeaderContents) { if (!FileInfo.AllowSaveExportedHeaders()) { // Return false indicating that the header did not need updating return false; } static bool bTestedCmdLine = false; if (!bTestedCmdLine) { FScopeLock Lock(&TestCommandLineCS); if (!bTestedCmdLine) { const FString& ProjectSavedDir = FPaths::ProjectSavedDir(); if (FParse::Param(FCommandLine::Get(), TEXT("WRITEREF"))) { const FString ReferenceGeneratedCodePath = ProjectSavedDir / TEXT("ReferenceGeneratedCode/"); bWriteContents = true; UE_LOG(LogCompile, Log, TEXT("********************************* Writing reference generated code to %s."), *ReferenceGeneratedCodePath); UE_LOG(LogCompile, Log, TEXT("********************************* Deleting all files in ReferenceGeneratedCode.")); IFileManager::Get().DeleteDirectory(*ReferenceGeneratedCodePath, false, true); IFileManager::Get().MakeDirectory(*ReferenceGeneratedCodePath); } else if (FParse::Param(FCommandLine::Get(), TEXT("VERIFYREF"))) { const FString ReferenceGeneratedCodePath = ProjectSavedDir / TEXT("ReferenceGeneratedCode/"); const FString VerifyGeneratedCodePath = ProjectSavedDir / TEXT("VerifyGeneratedCode/"); bVerifyContents = true; UE_LOG(LogCompile, Log, TEXT("********************************* Writing generated code to %s and comparing to %s"), *VerifyGeneratedCodePath, *ReferenceGeneratedCodePath); UE_LOG(LogCompile, Log, TEXT("********************************* Deleting all files in VerifyGeneratedCode.")); IFileManager::Get().DeleteDirectory(*VerifyGeneratedCodePath, false, true); IFileManager::Get().MakeDirectory(*VerifyGeneratedCodePath); } bTestedCmdLine = true; } } if (bWriteContents || bVerifyContents) { // UHT is getting fast enough that we can create an I/O storm in these large directories. // The lock limits us to writing one file at a time. It doesn't impact performance // significantly. static FCriticalSection WritePacer; const FString& ProjectSavedDir = FPaths::ProjectSavedDir(); const FString CleanFilename = FPaths::GetCleanFilename(FileInfo.GetFilename()); const FString Ref = ProjectSavedDir / TEXT("ReferenceGeneratedCode") / CleanFilename; if (bWriteContents) { FScopeLock Lock(&WritePacer); bool Written = FFileHelper::SaveStringToFile(InNewHeaderContents, *Ref); check(Written); } else { { FScopeLock Lock(&WritePacer); const FString Verify = ProjectSavedDir / TEXT("VerifyGeneratedCode") / CleanFilename; bool Written = FFileHelper::SaveStringToFile(InNewHeaderContents, *Verify); check(Written); } FString RefHeader; FString Message; { SCOPE_SECONDS_COUNTER_UHT(LoadHeaderContentFromFile); if (!FFileHelper::LoadFileToString(RefHeader, *Ref)) { Message = FString::Printf(TEXT("********************************* %s appears to be a new generated file."), *CleanFilename); } else { if (FCString::Strcmp(*InNewHeaderContents, *RefHeader) != 0) { Message = FString::Printf(TEXT("********************************* %s has changed."), *CleanFilename); } } } if (Message.Len()) { UE_LOG(LogCompile, Log, TEXT("%s"), *Message); ChangeMessages.AddUnique(MoveTemp(Message)); } } } FString HeaderPathStr = FileInfo.GetFilename(); const FString& OriginalContents = FileInfo.GetOriginalContents(); const bool bHasChanged = OriginalContents.Len() != InNewHeaderContents.Len() || FCString::Strcmp(*OriginalContents, *InNewHeaderContents); if (bHasChanged) { static const bool bFailIfGeneratedCodeChanges = FParse::Param(FCommandLine::Get(), TEXT("FailIfGeneratedCodeChanges")); if (bFailIfGeneratedCodeChanges) { FString ConflictPath = HeaderPathStr + TEXT(".conflict"); FFileHelper::SaveStringToFile(InNewHeaderContents, *ConflictPath); FResults::SetResult(ECompilationResult::FailedDueToHeaderChange); FUHTMessage(FileInfo.GetFilename()).Throwf(TEXT("ERROR: '%s': Changes to generated code are not allowed - conflicts written to '%s'"), *HeaderPathStr, *ConflictPath); } // save the updated version to a tmp file so that the user can see what will be changing FString TmpHeaderFilename = GenerateTempHeaderName(HeaderPathStr, false); auto SaveTempTask = [&FileInfo, TmpHeaderFilename, InNewHeaderContents = MoveTemp(InNewHeaderContents)]() { // delete any existing temp file IFileManager::Get().Delete(*TmpHeaderFilename, false, true); if (!FFileHelper::SaveStringToFile(InNewHeaderContents, *TmpHeaderFilename)) { FUHTMessage(FileInfo.GetFilename()).LogWarning(TEXT("Failed to save header export preview: '%s'"), *TmpHeaderFilename); } }; FileInfo.SetSaveTaskRef(FFunctionGraphTask::CreateAndDispatchWhenReady(MoveTemp(SaveTempTask), TStatId())); FileInfo.SetTempFilename(MoveTemp(TmpHeaderFilename)); } // Remember this header filename to be able to check for any old (unused) headers later. HeaderPathStr.ReplaceInline(TEXT("\\"), TEXT("/"), ESearchCase::CaseSensitive); FileInfo.SetPackageFilename(MoveTemp(HeaderPathStr)); return bHasChanged; } FString FNativeClassHeaderGenerator::GenerateTempHeaderName( const FString& CurrentFilename, bool bReverseOperation ) { if (bReverseOperation) { FString Reversed = CurrentFilename; Reversed.RemoveFromEnd(TEXT(".tmp"), ESearchCase::CaseSensitive); return Reversed; } return CurrentFilename + TEXT(".tmp"); } void FNativeClassHeaderGenerator::ExportUpdatedHeaders(FString&& PackageName, TArray&& TempHeaderPaths, FGraphEventArray& InTempSaveTasks) { // Asynchronously move the headers to the correct locations if (TempHeaderPaths.Num() > 0) { auto MoveHeadersTask = [PackageName = MoveTemp(PackageName), TempHeaderPaths = MoveTemp(TempHeaderPaths)]() { ParallelFor(TempHeaderPaths.Num(), [&](int32 Index) { const FString& TmpFilename = TempHeaderPaths[Index]; FString Filename = GenerateTempHeaderName(TmpFilename, true); if (!IFileManager::Get().Move(*Filename, *TmpFilename, true, true)) { UE_LOG(LogCompile, Error, TEXT("Error exporting %s: couldn't write file '%s'"), *PackageName, *Filename); } else { UE_LOG(LogCompile, Log, TEXT("Exported updated C++ header: %s"), *Filename); } }); }; FTaskGraphInterface::Get().WaitUntilTasksComplete(InTempSaveTasks); GAsyncFileTasks.Add(FFunctionGraphTask::CreateAndDispatchWhenReady(MoveTemp(MoveHeadersTask), TStatId())); } } /** Get all script plugins based on ini setting */ void GetScriptPlugins(TArray& ScriptPlugins) { if (!GManifest.IsGameTarget) { UE_LOG(LogCompile, Log, TEXT("Script generator plugins only enabled in game targets.")); } ScriptPlugins = IModularFeatures::Get().GetModularFeatureImplementations(TEXT("ScriptGenerator")); UE_LOG(LogCompile, Log, TEXT("Found %d script generator plugins."), ScriptPlugins.Num()); // Check if we can use these plugins and initialize them for (int32 PluginIndex = ScriptPlugins.Num() - 1; PluginIndex >= 0; --PluginIndex) { IScriptGeneratorPluginInterface* ScriptGenerator = ScriptPlugins[PluginIndex]; bool bSupportedPlugin = ScriptGenerator->SupportsTarget(GManifest.TargetName); if (bSupportedPlugin) { // Find the right output directory for this plugin base on its target (Engine-side) plugin name. FString GeneratedCodeModuleName = ScriptGenerator->GetGeneratedCodeModuleName(); const FManifestModule* GeneratedCodeModule = NULL; FString OutputDirectory; FString IncludeBase; for (const FManifestModule& Module : GManifest.Modules) { if (Module.Name == GeneratedCodeModuleName) { GeneratedCodeModule = &Module; } } if (GeneratedCodeModule) { UE_LOG(LogCompile, Log, TEXT("Initializing script generator \'%s\'"), *ScriptGenerator->GetGeneratorName()); ScriptGenerator->Initialize(GManifest.RootLocalPath, GManifest.RootBuildPath, GeneratedCodeModule->GeneratedIncludeDirectory, GeneratedCodeModule->IncludeBase); } else { // Can't use this plugin UE_LOG(LogCompile, Log, TEXT("Unable to determine output directory for %s. Cannot export script glue with \'%s\'"), *GeneratedCodeModuleName, *ScriptGenerator->GetGeneratorName()); bSupportedPlugin = false; } } if (!bSupportedPlugin) { UE_LOG(LogCompile, Log, TEXT("Script generator \'%s\' not supported for target: %s"), *ScriptGenerator->GetGeneratorName(), *GManifest.TargetName); ScriptPlugins.RemoveAt(PluginIndex); } } } /** * Tries to resolve super classes for classes defined in the given class */ void ResolveSuperClasses(const TCHAR* PackageName, FUnrealClassDefinitionInfo& ClassDef) { // Resolve the base class { FUnrealStructDefinitionInfo::FBaseStructInfo& SuperClassInfo = ClassDef.GetSuperStructInfo(); if (!SuperClassInfo.Name.IsEmpty()) { const FString& BaseClassName = SuperClassInfo.Name; const FString& BaseClassNameStripped = GetClassNameWithPrefixRemoved(BaseClassName); FUnrealClassDefinitionInfo* FoundBaseClassDef = GTypeDefinitionInfoMap.FindByName(*BaseClassNameStripped); if (FoundBaseClassDef == nullptr) { FoundBaseClassDef = GTypeDefinitionInfoMap.FindByName(*BaseClassName); } if (FoundBaseClassDef == nullptr) { // Don't know its parent class. Raise error. ClassDef.Throwf(TEXT("Couldn't find parent type for '%s' named '%s' in current module (Package: %s) or any other module parsed so far."), *ClassDef.GetName(), *BaseClassName, PackageName); } SuperClassInfo.Struct = FoundBaseClassDef; ClassDef.SetClassCastFlags(FoundBaseClassDef->GetClassCastFlags()); } } // Resolve the inherited classes { for (FUnrealStructDefinitionInfo::FBaseStructInfo& BaseClassInfo : ClassDef.GetBaseStructInfos()) { BaseClassInfo.Struct = FUnrealClassDefinitionInfo::FindScriptClass(BaseClassInfo.Name); } } } /** * Tries to resolve super classes for classes defined in the given * module. * * @param Package Modules package. */ void ResolveSuperClasses(FUnrealPackageDefinitionInfo& PackageDef) { FString PackageName = PackageDef.GetName(); for (TSharedRef SourceFile : PackageDef.GetAllSourceFiles()) { for (TSharedRef TypeDef : SourceFile->GetDefinedClasses()) { ResolveSuperClasses(*PackageName, UHTCastChecked(TypeDef)); } } } UPackage* GetModulePackage(FManifestModule& Module) { UPackage* Package = FEngineAPI::FindObjectFast(NULL, FName(*Module.LongPackageName), false, false); if (Package == NULL) { Package = CreatePackage(*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->SetPackageFlags(PKG_ContainsScript | PKG_Compiling); Package->ClearPackageFlags(PKG_ClientOptional | PKG_ServerSideOnly); if (Module.OverrideModuleType == EPackageOverrideType::None) { switch (Module.ModuleType) { case EBuildModuleType::GameEditor: case EBuildModuleType::EngineEditor: Package->SetPackageFlags(PKG_EditorOnly); break; case EBuildModuleType::GameDeveloper: case EBuildModuleType::EngineDeveloper: Package->SetPackageFlags(PKG_Developer); break; case EBuildModuleType::GameUncooked: case EBuildModuleType::EngineUncooked: Package->SetPackageFlags(PKG_UncookedOnly); break; } } else { // If the user has specified this module to have another package flag, then OR it on switch (Module.OverrideModuleType) { case EPackageOverrideType::EditorOnly: Package->SetPackageFlags(PKG_EditorOnly); break; case EPackageOverrideType::EngineDeveloper: case EPackageOverrideType::GameDeveloper: Package->SetPackageFlags(PKG_Developer); break; case EPackageOverrideType::EngineUncookedOnly: case EPackageOverrideType::GameUncookedOnly: Package->SetPackageFlags(PKG_UncookedOnly); break; } } return Package; } void PrepareModules(TArray& PackageDefs, const FString& ModuleInfoPath) { // Three passes. 1) Public 'Classes' headers (legacy) 2) Public headers 3) Private headers enum EHeaderFolderTypes { PublicClassesHeaders = 0, PublicHeaders, InternalHeaders, PrivateHeaders, FolderType_Count }; for (int32 ModuleIndex = 0, NumModules = GManifest.Modules.Num(); ModuleIndex < NumModules; ++ModuleIndex) { FManifestModule& Module = GManifest.Modules[ModuleIndex]; // Force regeneration of all subsequent modules, otherwise data will get corrupted. Module.ForceRegeneration(); UPackage* Package = GetModulePackage(Module); // Create the package definition TSharedRef PackageDefRef = MakeShared(Module, Package); FUnrealPackageDefinitionInfo& PackageDef = *PackageDefRef; GTypeDefinitionInfoMap.AddNameLookup(PackageDef); PackageDefs.Add(&PackageDef); TArray>& AllSourceFiles = PackageDef.GetAllSourceFiles(); AllSourceFiles.Reserve(Module.PublicUObjectClassesHeaders.Num() + Module.PublicUObjectHeaders.Num() + Module.InternalUObjectHeaders.Num() + Module.PrivateUObjectHeaders.Num()); // Initialize the other header data structures and create the unreal source files for (int32 PassIndex = 0; PassIndex < FolderType_Count && FResults::IsSucceeding(); ++PassIndex) { EHeaderFolderTypes CurrentlyProcessing = (EHeaderFolderTypes)PassIndex; const TArray& UObjectHeaders = (CurrentlyProcessing == PublicClassesHeaders) ? Module.PublicUObjectClassesHeaders : (CurrentlyProcessing == PublicHeaders) ? Module.PublicUObjectHeaders : (CurrentlyProcessing == InternalHeaders) ? Module.InternalUObjectHeaders : Module.PrivateUObjectHeaders; if (UObjectHeaders.Num() == 0) { continue; } // Create the unreal source file objects for each header for (int32 Index = 0, EIndex = UObjectHeaders.Num(); Index < EIndex; ++Index) { const FString& RawFilename = UObjectHeaders[Index]; const FString FullFilename = FPaths::ConvertRelativePathToFull(ModuleInfoPath, RawFilename); FUnrealSourceFile* UnrealSourceFilePtr = new FUnrealSourceFile(PackageDef, RawFilename); TSharedRef UnrealSourceFile(UnrealSourceFilePtr); AllSourceFiles.Add(UnrealSourceFile); FString CleanFilename = FPaths::GetCleanFilename(RawFilename); uint32 CleanFilenameHash = GetTypeHash(CleanFilename); if (TSharedPtr ExistingSourceFile = GUnrealSourceFilesMap.AddByHash(CleanFilenameHash, MoveTemp(CleanFilename), UnrealSourceFile)) { FString NormalizedFullFilename = FullFilename; FString NormalizedExistingFilename = ExistingSourceFile->GetFilename(); FPaths::NormalizeFilename(NormalizedFullFilename); FPaths::NormalizeFilename(NormalizedExistingFilename); if (NormalizedFullFilename != NormalizedExistingFilename) { FUHTMessage(*UnrealSourceFile).LogError(TEXT("Duplicate leaf header name found: %s (original: %s)"), *NormalizedFullFilename, *NormalizedExistingFilename); } } if (CurrentlyProcessing == PublicClassesHeaders) { UnrealSourceFilePtr->MarkPublic(); } // Save metadata for the class path, both for it's include path and relative to the module base directory if (FullFilename.StartsWith(Module.BaseDirectory)) { // Get the path relative to the module directory const TCHAR* ModuleRelativePath = *FullFilename + Module.BaseDirectory.Len(); UnrealSourceFilePtr->SetModuleRelativePath(ModuleRelativePath); // Calculate the include path const TCHAR* IncludePath = ModuleRelativePath; // Walk over the first potential slash if (*IncludePath == TEXT('/')) { IncludePath++; } // Does this module path start with a known include path location? If so, we can cut that part out of the include path static const auto& PublicFolderName = TEXT("Public/"); static const auto& PrivateFolderName = TEXT("Private/"); static const auto& ClassesFolderName = TEXT("Classes/"); static const auto& InternalFolderName = TEXT("Internal/"); if (FCString::Strnicmp(IncludePath, PublicFolderName, UE_ARRAY_COUNT(PublicFolderName) - 1) == 0) { IncludePath += (UE_ARRAY_COUNT(PublicFolderName) - 1); } else if (FCString::Strnicmp(IncludePath, PrivateFolderName, UE_ARRAY_COUNT(PrivateFolderName) - 1) == 0) { IncludePath += (UE_ARRAY_COUNT(PrivateFolderName) - 1); } else if (FCString::Strnicmp(IncludePath, ClassesFolderName, UE_ARRAY_COUNT(ClassesFolderName) - 1) == 0) { IncludePath += (UE_ARRAY_COUNT(ClassesFolderName) - 1); } else if (FCString::Strnicmp(IncludePath, InternalFolderName, UE_ARRAY_COUNT(InternalFolderName) - 1) == 0) { IncludePath += (UE_ARRAY_COUNT(InternalFolderName) - 1); } // Add the include path if (*IncludePath != 0) { UnrealSourceFilePtr->SetIncludePath(MoveTemp(IncludePath)); } } } } } GUnrealSourceFilesMap.Freeze(); } void LoadSource(FUnrealSourceFile& SourceFile, const FString& ModuleInfoPath) { FResults::Try([&SourceFile, &ModuleInfoPath]() { FScopedDurationTimer SourceTimer(SourceFile.GetTime(ESourceFileTime::Load)); const FString FullFilename = FPaths::ConvertRelativePathToFull(ModuleInfoPath, *SourceFile.GetFilename()); FString Content; if (!FFileHelper::LoadFileToString(Content, *FullFilename)) { FUHTMessage(SourceFile).Throwf(TEXT("UnrealHeaderTool was unable to load source file '%s'"), *FullFilename); } SourceFile.SetContent(MoveTemp(Content)); } ); } void PreparseSource(FUnrealSourceFile& SourceFile) { FResults::Try([&SourceFile]() { FScopedDurationTimer SourceTimer(SourceFile.GetTime(ESourceFileTime::PreParse)); // Parse the header to extract the information needed FUHTStringBuilder ClassHeaderTextStrippedOfCppText; FHeaderParser::SimplifiedClassParse(SourceFile, *SourceFile.GetContent(), /*out*/ ClassHeaderTextStrippedOfCppText); SourceFile.SetContent(MoveTemp(ClassHeaderTextStrippedOfCppText)); } ); } void PreparseSources(TArray& PackageDefs, const FString& ModuleInfoPath) { #if UHT_ENABLE_CONCURRENT_PREPARSING FGraphEventArray LoadTasks; LoadTasks.Reserve(1024); // Fairly arbitrary number for (FUnrealPackageDefinitionInfo* PackageDef : PackageDefs) { for (TSharedRef& SourceFile : PackageDef->GetAllSourceFiles()) { // Phase #1: Load the file auto LoadLambda = [&SourceFile = *SourceFile, &ModuleInfoPath]() { LoadSource(SourceFile, ModuleInfoPath); }; // Phase #2: Perform simplified class parse (can run concurrenrtly) auto PreProcessLambda = [&SourceFile = *SourceFile]() { PreparseSource(SourceFile); }; FGraphEventRef LoadTask = FFunctionGraphTask::CreateAndDispatchWhenReady(MoveTemp(LoadLambda), TStatId()); FGraphEventRef PreProcessTask = FFunctionGraphTask::CreateAndDispatchWhenReady(MoveTemp(PreProcessLambda), TStatId(), LoadTask); LoadTasks.Add(MoveTemp(PreProcessTask)); } } // Wait for all the loading and preparsing to complete FTaskGraphInterface::Get().WaitUntilTasksComplete(LoadTasks); #else for (FUnrealPackageDefinitionInfo* PackageDef : PackageDefs) { for (TSharedRef& SourceFile : PackageDef->GetAllSourceFiles()) { LoadSource(*SourceFile, ModuleInfoPath); PreparseSource(*SourceFile); } } #endif FResults::WaitForErrorTasks(); } void DefineTypes(TArray& PackageDefs) { for (FUnrealPackageDefinitionInfo* PackageDef : PackageDefs) { const FManifestModule& Module = PackageDef->GetModule(); for (TSharedRef& SourceFile : PackageDef->GetAllSourceFiles()) { FResults::Try([PackageDef, &SourceFile = *SourceFile]() { TArray>& AllClasses = PackageDef->GetAllClasses(); UPackage* Package = PackageDef->GetPackage(); for (TSharedRef& TypeDef : SourceFile.GetDefinedClasses()) { FUnrealClassDefinitionInfo& ClassDef = TypeDef->AsClassChecked(); ProcessParsedClass(ClassDef); GTypeDefinitionInfoMap.AddNameLookup(UHTCastChecked(TypeDef)); AllClasses.Add(TypeDef); } for (TSharedRef& TypeDef : SourceFile.GetDefinedEnums()) { FUnrealEnumDefinitionInfo& EnumDef = TypeDef->AsEnumChecked(); ProcessParsedEnum(EnumDef); GTypeDefinitionInfoMap.AddNameLookup(UHTCastChecked(TypeDef)); } for (TSharedRef& TypeDef : SourceFile.GetDefinedStructs()) { FUnrealScriptStructDefinitionInfo& ScriptStructDef = TypeDef->AsScriptStructChecked(); ProcessParsedStruct(ScriptStructDef); GTypeDefinitionInfoMap.AddNameLookup(UHTCastChecked(TypeDef)); } static const bool bVerbose = FParse::Param(FCommandLine::Get(), TEXT("VERBOSE")); if (bVerbose) { for (FHeaderProvider& DependsOnElement : SourceFile.GetIncludes()) { UE_LOG(LogCompile, Log, TEXT("\tAdding %s as a dependency"), *DependsOnElement.ToString()); } } } ); } } FResults::WaitForErrorTasks(); } void ResolveParents(TArray& PackageDefs) { GUObjectDef = >ypeDefinitionInfoMap.FindByNameChecked(*UObject::StaticClass()->GetFName().ToString()); GUClassDef = >ypeDefinitionInfoMap.FindByNameChecked(*UClass::StaticClass()->GetFName().ToString()); GUInterfaceDef = >ypeDefinitionInfoMap.FindByNameChecked(*UInterface::StaticClass()->GetFName().ToString()); for (FUnrealPackageDefinitionInfo* PackageDef : PackageDefs) { FResults::Try([PackageDef]() { ResolveSuperClasses(*PackageDef); }); } FResults::WaitForErrorTasks(); } void PrepareTypesForParsing(TArray& PackageDefs) { // Does nothing now } void TopologicalRecursion(FUnrealSourceFile& First, FUnrealSourceFile& Visit) { check(Visit.GetTopologicalState() == ETopologicalState::Temporary); for (FHeaderProvider& Header : Visit.GetIncludes()) { if (FUnrealSourceFile* Include = Header.Resolve(Visit)) { if (Include->GetTopologicalState() == ETopologicalState::Temporary) { UE_LOG(LogCompile, Error, TEXT("%s includes/requires %s"), *Visit.GetFilename(), *Include->GetFilename()); if (&First != Include) { TopologicalRecursion(First, *Include); } break; } } } } FUnrealSourceFile* TopologicalVisit(TArray& OrderedSourceFiles, FUnrealSourceFile& Visit) { switch (Visit.GetTopologicalState()) { case ETopologicalState::Unmarked: Visit.SetTopologicalState(ETopologicalState::Temporary); for (FHeaderProvider& Header : Visit.GetIncludes()) { if (FUnrealSourceFile* Include = Header.Resolve(Visit)) { if (FUnrealSourceFile* Recursion = TopologicalVisit(OrderedSourceFiles, *Include)) { return Recursion; } } } Visit.SetTopologicalState(ETopologicalState::Permanent); OrderedSourceFiles.Add(&Visit); return nullptr; case ETopologicalState::Temporary: return &Visit; case ETopologicalState::Permanent: return nullptr; } return nullptr; } void TopologicalSort(TArray& OrderedSourceFiles) { const TArray& UnorderedSourceFiles = GUnrealSourceFilesMap.GetAllSourceFiles(); OrderedSourceFiles.Reset(UnorderedSourceFiles.Num()); for (FUnrealSourceFile* SourceFile : UnorderedSourceFiles) { SourceFile->SetTopologicalState(ETopologicalState::Unmarked); } for (FUnrealSourceFile* SourceFile : UnorderedSourceFiles) { if (SourceFile->GetTopologicalState() == ETopologicalState::Unmarked) { if (FUnrealSourceFile* Recusion = TopologicalVisit(OrderedSourceFiles, *SourceFile)) { UE_LOG(LogCompile, Error, TEXT("Circular dependency detected:")); TopologicalRecursion(*Recusion, *Recusion); FResults::SetResult(ECompilationResult::OtherCompilationError); return; } } } for (int32 Index = 0, EIndex = OrderedSourceFiles.Num(); Index != EIndex; ++Index) { OrderedSourceFiles[Index]->SetOrderedIndex(Index); } return; } void ParseSourceFiles(TArray& OrderedSourceFiles) { // Disable loading of objects outside of this package (or more exactly, objects which aren't UFields, CDO, or templates) TGuardValue AutoRestoreVerifyObjectRefsFlag(GVerifyObjectReferencesOnly, true); GSourcesToParse = (int)OrderedSourceFiles.Num(); GSourcesConcurrent = UHT_ENABLE_CONCURRENT_PARSING != 0; #if UHT_ENABLE_CONCURRENT_PARSING /** * For every FUnrealSourceFile being processed, an instance of this class represents the data associated with generating the new output. */ struct FParseCPP { FParseCPP(FUnrealPackageDefinitionInfo& InPackageDef, FUnrealSourceFile& InSourceFile) : PackageDef(InPackageDef) , SourceFile(InSourceFile) {} /** * The package definition being exported */ FUnrealPackageDefinitionInfo& PackageDef; /** * The source file being exported */ FUnrealSourceFile& SourceFile; /** * This task represents the task that parses the source */ FGraphEventRef ParseTaskRef; }; TArray ParsedCPPs; ParsedCPPs.Reserve(OrderedSourceFiles.Num()); for (FUnrealSourceFile* SourceFile : OrderedSourceFiles) { ParsedCPPs.Emplace(SourceFile->GetPackageDef(), *SourceFile); } TSet Includes; Includes.Reserve(ParsedCPPs.Num()); FGraphEventArray TempTasks; TempTasks.Reserve(ParsedCPPs.Num()); FGraphEventArray ParsedSourceTasks; ParsedSourceTasks.Reserve(ParsedCPPs.Num()); for (FParseCPP& ParsedCPP : ParsedCPPs) { const FManifestModule& Module = ParsedCPP.PackageDef.GetModule(); FUnrealSourceFile& SourceFile = ParsedCPP.SourceFile; FString ModuleRelativeFilename = SourceFile.GetFilename(); ConvertToBuildIncludePath(Module, ModuleRelativeFilename); auto ParseSource = [&ParsedCPP]() { ++GSourcesParsing; FResults::TryAlways([&ParsedCPP]() { FHeaderParser::Parse(ParsedCPP.PackageDef, ParsedCPP.SourceFile); }); ++GSourcesCompleted; }; Includes.Reset(); for (FHeaderProvider& Header : SourceFile.GetIncludes()) { if (FUnrealSourceFile* Include = Header.Resolve(SourceFile)) { Includes.Add(Include); } } // Our generation must wait on all of our includes generation to complete TempTasks.Reset(); for (FUnrealSourceFile* Include : Includes) { FParseCPP& IncludeCPP = ParsedCPPs[Include->GetOrderedIndex()]; TempTasks.Add(IncludeCPP.ParseTaskRef); } ParsedCPP.ParseTaskRef = FFunctionGraphTask::CreateAndDispatchWhenReady(MoveTemp(ParseSource), TStatId(), &TempTasks); ParsedSourceTasks.Add(ParsedCPP.ParseTaskRef); } // Wait for the results FTaskGraphInterface::Get().WaitUntilTasksComplete(ParsedSourceTasks); FResults::WaitForErrorTasks(); #else for (FUnrealSourceFile* SourceFile : OrderedSourceFiles) { FUnrealPackageDefinitionInfo& PackageDef = SourceFile->GetPackageDef(); FScopedDurationTimer SourceTimer(SourceFile->GetTime(ESourceFileTime::Parse)); ++GSourcesParsing; FResults::TryAlways([&PackageDef, SourceFile]() { FHeaderParser::Parse(PackageDef, *SourceFile); }); ++GSourcesCompleted; } #endif } void PostParseFinalize(TArray& PackageDefs) { auto PostParseFinalize = [&PackageDefs](EPostParseFinalizePhase Phase) { for (FUnrealPackageDefinitionInfo* PackageDef : PackageDefs) { FResults::Try([PackageDef, Phase]() { PackageDef->PostParseFinalize(Phase); }); } }; PostParseFinalize(EPostParseFinalizePhase::Phase1); PostParseFinalize(EPostParseFinalizePhase::Phase2); FResults::WaitForErrorTasks(); } void CreateEngineTypes(TArray& PackageDefs) { auto CreateEngineTypes = [&PackageDefs](ECreateEngineTypesPhase Phase) { for (FUnrealPackageDefinitionInfo* PackageDef : PackageDefs) { FResults::Try([PackageDef, Phase]() { PackageDef->CreateUObjectEngineTypes(Phase); }); } for (FUnrealPackageDefinitionInfo* PackageDef : PackageDefs) { FResults::Try([PackageDef, Phase]() { PackageDef->CreateUObjectEngineTypes(Phase); }); } }; CreateEngineTypes(ECreateEngineTypesPhase::Phase1); CreateEngineTypes(ECreateEngineTypesPhase::Phase2); CreateEngineTypes(ECreateEngineTypesPhase::Phase3); FResults::WaitForErrorTasks(); } void Export(TArray& PackageDefs, TArray& OrderedSourceFiles) { TArray GeneratedCPPs; GeneratedCPPs.Reserve(OrderedSourceFiles.Num()); for (FUnrealSourceFile* SourceFile : OrderedSourceFiles) { GeneratedCPPs.Emplace(SourceFile->GetPackageDef(), *SourceFile); } FResults::Try([&GeneratedCPPs]() { FNativeClassHeaderGenerator::GenerateSourceFiles(GeneratedCPPs); }); for (FUnrealPackageDefinitionInfo* PackageDef : PackageDefs) { FResults::Try([&GeneratedCPPs, PackageDef]() { FNativeClassHeaderGenerator::Generate(*PackageDef, GeneratedCPPs); }); } FResults::WaitForErrorTasks(); } // Exports the class to all available plugins void ExportClassToScriptPlugins(const TMap& SourceFileLookup, UClass* Class, const FManifestModule& Module, IScriptGeneratorPluginInterface& ScriptPlugin) { check(SourceFileLookup.Find(Class) != nullptr); FUnrealSourceFile* const * SourceFile = SourceFileLookup.Find(Class); if (SourceFile == nullptr) { const FString Empty = TEXT(""); ScriptPlugin.ExportClass(Class, Empty, Empty, false); } else { ScriptPlugin.ExportClass(Class, (*SourceFile)->GetFilename(), (*SourceFile)->GetGeneratedFilename(), (*SourceFile)->HasChanged()); } } // Exports class tree to all available plugins void ExportClassTreeToScriptPlugins(const TMap& SourceFileLookup, const FClassTree* Node, const FManifestModule& Module, IScriptGeneratorPluginInterface& ScriptPlugin) { for (int32 ChildIndex = 0; ChildIndex < Node->NumChildren(); ++ChildIndex) { const FClassTree* ChildNode = Node->GetChild(ChildIndex); ExportClassToScriptPlugins(SourceFileLookup, ChildNode->GetClass(), Module, ScriptPlugin); } for (int32 ChildIndex = 0; ChildIndex < Node->NumChildren(); ++ChildIndex) { const FClassTree* ChildNode = Node->GetChild(ChildIndex); ExportClassTreeToScriptPlugins(SourceFileLookup, ChildNode, Module, ScriptPlugin); } } void ExportToScriptPlugins(TArray& ScriptPlugins, TArray& PackageDefs, FString& ExternalDependencies) { TMap SourceFileLookup; for (FUnrealPackageDefinitionInfo* PackageDef : PackageDefs) { for (TSharedRef TypeDef : PackageDef->GetAllClasses()) { UClass* Class = UHTCastChecked(TypeDef).GetClass(); SourceFileLookup.Add(Class, &TypeDef->GetUnrealSourceFile()); } } for (FUnrealPackageDefinitionInfo* PackageDef : PackageDefs) { const FManifestModule& Module = PackageDef->GetModule(); FClassTree ClassTree(UObject::StaticClass()); for (TSharedRef TypeDef : PackageDef->GetAllClasses()) { ClassTree.AddClass(UHTCastChecked(TypeDef).GetClass()); } ClassTree.Validate(); for (IScriptGeneratorPluginInterface* Plugin : ScriptPlugins) { if (Plugin->ShouldExportClassesForModule(Module.Name, Module.ModuleType, Module.GeneratedIncludeDirectory)) { ExportClassToScriptPlugins(SourceFileLookup, ClassTree.GetClass(), Module, *Plugin); ExportClassTreeToScriptPlugins(SourceFileLookup, &ClassTree, Module, *Plugin); } } } for (IScriptGeneratorPluginInterface* ScriptGenerator : ScriptPlugins) { ScriptGenerator->FinishExport(); } // Get a list of external dependencies from each enabled plugin for (IScriptGeneratorPluginInterface* ScriptPlugin : ScriptPlugins) { TArray PluginExternalDependencies; ScriptPlugin->GetExternalDependencies(PluginExternalDependencies); for (const FString& PluginExternalDependency : PluginExternalDependencies) { ExternalDependencies += PluginExternalDependency + LINE_TERMINATOR; } } return; } void WriteExternalDependencies(const FString& ExternalDependencies) { FFileHelper::SaveStringToFile(ExternalDependencies, *GManifest.ExternalDependenciesFile); } void GenerateSummary(TArray& PackageDefs) { for (FUnrealPackageDefinitionInfo* PackageDef : PackageDefs) { const FManifestModule& Module = PackageDef->GetModule(); double TotalTimes[int32(ESourceFileTime::Count)] = { 0.0 }; int32 LinesParsed = 0; int32 StatementsParsed = 0; int32 SourceCount = 0; TArray>& SourceFiles = PackageDef->GetAllSourceFiles(); for (TSharedRef& SourceFile : SourceFiles) { for (int32 Index = 0; Index < int32(ESourceFileTime::Count); ++Index) { TotalTimes[int32(Index)] += SourceFile->GetTime(ESourceFileTime(Index)); } LinesParsed += SourceFile->GetLinesParsed(); StatementsParsed += SourceFile->GetStatementsParsed(); } UE_LOG(LogCompile, Log, TEXT("Success: Module %s parsed %d sources(s), %d line(s), %d statement(s). Times(secs) Load: %.3f, PreParse: %.3f, Parse: %.3f, Generate: %.3f."), *Module.Name, SourceFiles.Num(), LinesParsed, StatementsParsed, TotalTimes[int32(ESourceFileTime::Load)], TotalTimes[int32(ESourceFileTime::PreParse)], TotalTimes[int32(ESourceFileTime::Parse)], TotalTimes[int32(ESourceFileTime::Generate)]); } } ECompilationResult::Type UnrealHeaderTool_Main(const FString& ModuleInfoFilename) { double MainTime = 0.0; FDurationTimer MainTimer(MainTime); MainTimer.Start(); check(GIsUCCMakeStandaloneHeaderGenerator); FString ModuleInfoPath = FPaths::GetPath(ModuleInfoFilename); // The meta data keywords must be initialized prior to going wide FBaseParser::InitMetadataKeywords(); // Load the manifest file, giving a list of all modules to be processed, pre-sorted by dependency ordering FResults::Try([&ModuleInfoFilename]() { GManifest = FManifest::LoadFromFile(ModuleInfoFilename); }); TArray OrderedSourceFiles; TArray PackageDefs; PackageDefs.Reserve(GManifest.Modules.Num()); double TotalPrepareModuleTime = FResults::TimedTry([&PackageDefs, &ModuleInfoPath]() { PrepareModules(PackageDefs, ModuleInfoPath); }); FString ExternalDependencies; TArray ScriptPlugins; double TotalPreparseTime = FResults::TimedTry([&PackageDefs, &ModuleInfoPath]() { PreparseSources(PackageDefs, ModuleInfoPath); }); double TotalDefineTypesTime = FResults::TimedTry([&PackageDefs]() { DefineTypes(PackageDefs); }); double TotalResolveParentsTime = FResults::TimedTry([&PackageDefs]() { ResolveParents(PackageDefs); }); double TotalPrepareTypesForParsingTime = FResults::TimedTry([&PackageDefs]() { PrepareTypesForParsing(PackageDefs); }); double TotalTopologicalSortTime = FResults::TimedTry([&OrderedSourceFiles]() { TopologicalSort(OrderedSourceFiles); }); double TotalParseTime = FResults::TimedTry([&OrderedSourceFiles]() { ParseSourceFiles(OrderedSourceFiles); }); double TotalPostParseFinalizeTime = FResults::TimedTry([&PackageDefs]() { PostParseFinalize(PackageDefs); }); // Look for any core classes that don't have a definition for (TObjectIterator ClassIt; ClassIt; ++ClassIt) { if (GTypeDefinitionInfoMap.FindByName(*ClassIt->GetFName().ToString()) == nullptr) { UE_LOG(LogCompile, Log, TEXT("The core class '%s' doesn't have a matching entry in NoExportTypes.h"), *ClassIt->GetFName().ToString()); } } TotalTopologicalSortTime += FResults::TimedTry([&OrderedSourceFiles]() { TopologicalSort(OrderedSourceFiles); }); // Sort again to include new dependencies double TotalCodeGenTime = FResults::TimedTry([&PackageDefs, &OrderedSourceFiles]() { Export(PackageDefs, OrderedSourceFiles); }); double TotalCheckForScriptPluginsTime = FResults::TimedTry([&ScriptPlugins]() { GetScriptPlugins(ScriptPlugins); }); double TotalCreateEngineTypesTime = ScriptPlugins.IsEmpty() ? 0.0 : FResults::TimedTry([&PackageDefs]() { CreateEngineTypes(PackageDefs); }); double TotalPluginTime = ScriptPlugins.IsEmpty() ? 0.0 : FResults::TimedTry([&ScriptPlugins, &PackageDefs, &ExternalDependencies]() { ExportToScriptPlugins(ScriptPlugins, PackageDefs, ExternalDependencies); }); double TotalWriteExternalDependenciesTime = FResults::TimedTry([&ExternalDependencies]() { WriteExternalDependencies(ExternalDependencies); }); double TotalSummaryTime = FResults::TimedTry([&PackageDefs]() { GenerateSummary(PackageDefs); }); // Finish all async file tasks before stopping the clock FTaskGraphInterface::Get().WaitUntilTasksComplete(GAsyncFileTasks); GAsyncFileTasks.Reset(); // TEMPORARY change to log all files when UHT fails if (FResults::GetOverallResults() != 0) { UE_LOG(LogCompile, Log, TEXT("Source file listing due to UHT detected errors:")); for (FUnrealPackageDefinitionInfo* PackageDef : PackageDefs) { const FManifestModule& Module = PackageDef->GetModule(); UE_LOG(LogCompile, Log, TEXT("Package %s sources"), *Module.Name); TArray>& SourceFiles = PackageDef->GetAllSourceFiles(); for (TSharedRef& SourceFile : SourceFiles) { UE_LOG(LogCompile, Log, TEXT("---- %s"), *SourceFile->GetFilename()); } } } double TotalShutdownTime = FResults::TimedTry([]() { GTypeDefinitionInfoMap.Reset(); GUnrealSourceFilesMap.Reset(); }); MainTimer.Stop(); // Count the number of sources int NumSources = 0; for (const FManifestModule& Module : GManifest.Modules) { NumSources += Module.PublicUObjectClassesHeaders.Num() + Module.PublicUObjectHeaders.Num() + Module.InternalUObjectHeaders.Num() + Module.PrivateUObjectHeaders.Num(); } UE_LOG(LogCompile, Log, TEXT("Preparing %d modules took %.3f seconds"), GManifest.Modules.Num(), TotalPrepareModuleTime); UE_LOG(LogCompile, Log, TEXT("Preparsing %d sources took %.3f seconds"), NumSources, TotalPreparseTime); UE_LOG(LogCompile, Log, TEXT("Defining types took %.3f seconds"), TotalDefineTypesTime); UE_LOG(LogCompile, Log, TEXT("Resolving type parents took %.3f seconds"), TotalResolveParentsTime); UE_LOG(LogCompile, Log, TEXT("Preparing types for parsing took %.3f seconds"), TotalPrepareTypesForParsingTime); UE_LOG(LogCompile, Log, TEXT("Sorting files by dependencies took %.3f seconds"), TotalTopologicalSortTime); UE_LOG(LogCompile, Log, TEXT("Parsing took %.3f seconds"), TotalParseTime); UE_LOG(LogCompile, Log, TEXT("Post parse finalization took %.3f seconds"), TotalPostParseFinalizeTime); UE_LOG(LogCompile, Log, TEXT("Code generation took %.3f seconds"), TotalCodeGenTime); UE_LOG(LogCompile, Log, TEXT("Check for script plugins took %.3f seconds"), TotalCheckForScriptPluginsTime); UE_LOG(LogCompile, Log, TEXT("Create engine types took % .3f seconds"), TotalCreateEngineTypesTime); UE_LOG(LogCompile, Log, TEXT("ScriptPlugin overhead was %.3f seconds"), TotalPluginTime); UE_LOG(LogCompile, Log, TEXT("Write external dependencies overhead was %.3f seconds"), TotalWriteExternalDependenciesTime); UE_LOG(LogCompile, Log, TEXT("Summary generation took %.3f seconds"), TotalSummaryTime); UE_LOG(LogCompile, Log, TEXT("Macroize time was %.3f CPU seconds"), GMacroizeTime); UE_LOG(LogCompile, Log, TEXT("Freeing types was %.3f seconds"), TotalShutdownTime); FUnrealHeaderToolStats& Stats = FUnrealHeaderToolStats::Get(); for (const TPair& Pair : Stats.Counters) { FString CounterName = Pair.Key.ToString(); UE_LOG(LogCompile, Log, TEXT("%s timer was %.3f seconds"), *CounterName, Pair.Value); } UE_LOG(LogCompile, Log, TEXT("Total time was %.2f seconds"), MainTime); 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 RefFileNames; IFileManager::Get().FindFiles( RefFileNames, *(FPaths::ProjectSavedDir() / TEXT("ReferenceGeneratedCode/*.*")), true, false ); TArray VerFileNames; IFileManager::Get().FindFiles( VerFileNames, *(FPaths::ProjectSavedDir() / 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()); } if (ChangeMessages.Num() > 0 || RefFileNames.Num() != VerFileNames.Num()) { FResults::SetResult(ECompilationResult::OtherCompilationError); } } RequestEngineExit(TEXT("UnrealHeaderTool finished")); return FResults::GetOverallResults(); } void ProcessParsedClass(FUnrealClassDefinitionInfo& ClassDef) { UPackage* Package = ClassDef.GetPackageDef().GetPackage(); const FString& ClassName = ClassDef.GetNameCPP(); FString ClassNameStripped = GetClassNameWithPrefixRemoved(*ClassName); const FString& BaseClassName = ClassDef.GetSuperStructInfo().Name; // All classes must start with a valid unreal prefix if (!FHeaderParser::ClassNameHasValidPrefix(ClassName, ClassNameStripped)) { ClassDef.Throwf(TEXT("Invalid class name '%s'. The class name must have an appropriate prefix added (A for Actors, U for other classes)."), *ClassName); } if(FHeaderParser::IsReservedTypeName(ClassNameStripped)) { ClassDef.Throwf(TEXT("Invalid class name '%s'. Cannot use a reserved name ('%s')."), *ClassName, *ClassNameStripped); } // 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)) { ClassDef.Throwf(TEXT("No prefix or invalid identifier for base class %s.\nClass names must match Unreal prefix specifications (e.g., \"UObject\" or \"AActor\")"), *BaseClassName); } } //UE_LOG(LogCompile, Log, TEXT("Class: %s extends %s"),*ClassName,*BaseClassName); // Handle failure and non-class headers. if (BaseClassName.IsEmpty() && (ClassName != TEXT("UObject"))) { ClassDef.Throwf(TEXT("Class '%s' must inherit UObject or a UObject-derived class"), *ClassName); } if (ClassName == BaseClassName) { ClassDef.Throwf(TEXT("Class '%s' cannot inherit from itself"), *ClassName); } UClass* ResultClass = FEngineAPI::FindObject(ANY_PACKAGE, *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")); if (TSharedRef* Existing = GTypeDefinitionInfoMap.FindByName(*ClassNameStripped)) { ClassDef.Throwf(TEXT("Duplicate class name: %s also exists in file %s"), *ClassName, *(*Existing)->GetFilename()); } if (bVerboseOutput) { UE_LOG(LogCompile, Log, TEXT("Imported: %s"), *ClassDef.GetFullName()); } } void ProcessParsedEnum(FUnrealEnumDefinitionInfo& EnumDef) { UPackage* Package = EnumDef.GetPackageDef().GetPackage(); const FString& EnumName = EnumDef.GetNameCPP(); if (TSharedRef* Existing = GTypeDefinitionInfoMap.FindByName(*EnumName)) { EnumDef.Throwf(TEXT("Duplicate enum name: %s also exists in file %s"), *EnumName, *(*Existing)->GetFilename()); } // Check if the enum name is using a reserved keyword if (FHeaderParser::IsReservedTypeName(EnumName)) { EnumDef.Throwf(TEXT("enum: '%s' uses a reserved type name."), *EnumName); } } void ProcessParsedStruct(FUnrealScriptStructDefinitionInfo& ScriptStructDef) { const FString& StructName = ScriptStructDef.GetNameCPP(); FString StructNameStripped = GetClassNameWithPrefixRemoved(*StructName); if (TSharedRef* Existing = GTypeDefinitionInfoMap.FindByName(*StructNameStripped)) { ScriptStructDef.Throwf(TEXT("Duplicate struct name: %s also exists in file %s"), *StructNameStripped, *(*Existing)->GetFilename()); } // Check if the enum name is using a reserved keyword if (FHeaderParser::IsReservedTypeName(StructNameStripped)) { ScriptStructDef.Throwf(TEXT("struct: '%s' uses a reserved type name."), *StructNameStripped); } }