3 files changed
+1105
No files matched your search
@@ -0,0 +1,964 @@
|
||||
#include "SourceCodeGenerator.h"
|
||||
#include "Features/IModularFeatures.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "UObject/CoreNet.h"
|
||||
#include "UObject/Interface.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FSourceCodeGeneratorModule"
|
||||
|
||||
void FSourceCodeGeneratorModule::StartupModule()
|
||||
{
|
||||
IModularFeatures::Get().RegisterModularFeature(TEXT("ScriptGenerator"), this);
|
||||
}
|
||||
|
||||
void FSourceCodeGeneratorModule::ShutdownModule()
|
||||
{
|
||||
IModularFeatures::Get().UnregisterModularFeature(TEXT("ScriptGenerator"), this);
|
||||
}
|
||||
|
||||
FString FSourceCodeGeneratorModule::GetGeneratedCodeModuleName() const
|
||||
{
|
||||
return TEXT("UnrealCSharp");
|
||||
}
|
||||
|
||||
bool FSourceCodeGeneratorModule::SupportsTarget(const FString& TargetName) const
|
||||
{
|
||||
const auto SettingFile = FPaths::Combine(
|
||||
FPaths::ConvertRelativePathToFull(FPaths::GetPath(FPaths::GetProjectFilePath())),
|
||||
TEXT("Config/DefaultUnrealCSharpEditorSetting.ini"));
|
||||
|
||||
auto bEnableExport = false;
|
||||
|
||||
if (FPaths::FileExists(SettingFile))
|
||||
{
|
||||
GConfig->GetBool(
|
||||
TEXT("/Script/UnrealCSharpCore.UnrealCSharpEditorSetting"),
|
||||
TEXT("bEnableExport"),
|
||||
bEnableExport,
|
||||
SettingFile);
|
||||
}
|
||||
|
||||
return bEnableExport;
|
||||
}
|
||||
|
||||
bool FSourceCodeGeneratorModule::ShouldExportClassesForModule(const FString& ModuleName,
|
||||
EBuildModuleType::Type ModuleType,
|
||||
const FString& ModuleGeneratedIncludeDirectory) const
|
||||
{
|
||||
return ExportModules.Contains(ModuleName) &&
|
||||
(ModuleType == EBuildModuleType::EngineRuntime || ModuleType == EBuildModuleType::GameRuntime);
|
||||
}
|
||||
|
||||
void FSourceCodeGeneratorModule::Initialize(const FString& RootLocalPath, const FString& RootBuildPath,
|
||||
const FString& OutputDirectory, const FString& IncludeBase)
|
||||
{
|
||||
OutputPath = OutputDirectory;
|
||||
|
||||
const auto ProjectPath = FPaths::ConvertRelativePathToFull(FPaths::GetPath(FPaths::GetProjectFilePath()));
|
||||
|
||||
const auto ProjectSourcePath = FPaths::Combine(ProjectPath, TEXT("Source/"));
|
||||
|
||||
const auto ProjectPluginsPath = FPaths::Combine(ProjectPath, TEXT("Plugins/"));
|
||||
|
||||
const auto EngineSourcePath = FPaths::EngineSourceDir();
|
||||
|
||||
const auto EnginePluginsPath = FPaths::EnginePluginsDir();
|
||||
|
||||
const auto ConfigFilePath = FPaths::Combine(ProjectPath, TEXT("Config/DefaultUnrealCSharpEditorSetting.ini"));
|
||||
|
||||
const auto ConfigSection = FString(TEXT("/Script/UnrealCSharpCore.UnrealCSharpEditorSetting"));
|
||||
|
||||
GetModules(ProjectSourcePath, Project);
|
||||
|
||||
GetModules(ProjectSourcePath, HeaderPath);
|
||||
|
||||
GetModules(ProjectPluginsPath, HeaderPath);
|
||||
|
||||
GetPlugins(ProjectPluginsPath, HeaderPath);
|
||||
|
||||
GetModules(FPaths::Combine(EngineSourcePath, TEXT("Developer/")), HeaderPath);
|
||||
|
||||
GetModules(FPaths::Combine(EngineSourcePath, TEXT("Editor/")), HeaderPath);
|
||||
|
||||
GetModules(FPaths::Combine(EngineSourcePath, TEXT("Programs/")), HeaderPath);
|
||||
|
||||
GetModules(FPaths::Combine(EngineSourcePath, TEXT("Runtime/")), HeaderPath);
|
||||
|
||||
GetModules(EnginePluginsPath, HeaderPath);
|
||||
|
||||
GetPlugins(EnginePluginsPath, HeaderPath);
|
||||
|
||||
if (FPaths::FileExists(ConfigFilePath))
|
||||
{
|
||||
GConfig->GetArray(*ConfigSection, TEXT("+ExportModule"), ExportModules, ConfigFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
void FSourceCodeGeneratorModule::ExportClass(UClass* Class, const FString& SourceHeaderFilename,
|
||||
const FString& GeneratedHeaderFilename, bool bHasChanged)
|
||||
{
|
||||
if (CanExportClass(Class))
|
||||
{
|
||||
TSet<UClass*> DependencyClasses = {Class};
|
||||
|
||||
FBigStringBuilder StringBuilder;
|
||||
|
||||
FBigStringBuilder BodyBuilder;
|
||||
|
||||
StringBuilder.Append(TEXT(
|
||||
"#pragma once\r\n\r\n"
|
||||
"PRAGMA_DISABLE_DEPRECATION_WARNINGS\r\n\r\n"));
|
||||
|
||||
BodyBuilder.Appendf(TEXT(
|
||||
"namespace\r\n"
|
||||
"{\r\n"
|
||||
"\tstruct FRegister%s\r\n"
|
||||
"\t{\r\n"
|
||||
"\t\tFRegister%s()\r\n"
|
||||
"\t\t{\r\n"
|
||||
"\t\t\tTBindingClassBuilder<%s>(NAMESPACE_BINDING)\r\n"
|
||||
),
|
||||
*Class->GetName(),
|
||||
*Class->GetName(),
|
||||
*(Class->GetPrefixCPP() + Class->GetName())
|
||||
);
|
||||
|
||||
for (TFieldIterator<FProperty> Property(Class, EFieldIterationFlags::None); Property; ++Property)
|
||||
{
|
||||
if (CanExportProperty(*Property))
|
||||
{
|
||||
GetDependencyClasses(*Property, DependencyClasses);
|
||||
|
||||
ExportProperty(BodyBuilder, Class, *Property);
|
||||
}
|
||||
}
|
||||
|
||||
for (TFieldIterator<UFunction> Function(Class, EFieldIterationFlags::None); Function; ++Function)
|
||||
{
|
||||
if (CanExportFunction(*Function))
|
||||
{
|
||||
for (TFieldIterator<FProperty> Property(*Function); Property; ++Property)
|
||||
{
|
||||
GetDependencyClasses(*Property, DependencyClasses);
|
||||
}
|
||||
|
||||
ExportFunction(BodyBuilder, Class, *Function);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto DependencyClass : DependencyClasses)
|
||||
{
|
||||
StringBuilder.Append(GetInclude(DependencyClass));
|
||||
}
|
||||
|
||||
StringBuilder.Append(TEXT("\r\n"));
|
||||
|
||||
BodyBuilder.RemoveSuffix(2);
|
||||
|
||||
BodyBuilder.Appendf(TEXT(
|
||||
";\r\n"
|
||||
"\t\t}\r\n"
|
||||
"\t};\r\n"
|
||||
"\r\n"
|
||||
"\t[[maybe_unused]] FRegister%s Register%s;\r\n"
|
||||
"}\r\n\r\n"
|
||||
),
|
||||
*Class->GetName(),
|
||||
*Class->GetName()
|
||||
);
|
||||
|
||||
StringBuilder.Append(BodyBuilder);
|
||||
|
||||
StringBuilder.Append(TEXT("PRAGMA_ENABLE_DEPRECATION_WARNINGS\r\n"));
|
||||
|
||||
const auto FilePath = FPaths::Combine(OutputPath, *Class->GetName() + BindingSuffix);
|
||||
|
||||
SaveIfChanged(FilePath, StringBuilder.ToString());
|
||||
|
||||
ExportClasses.AddUnique(Class);
|
||||
}
|
||||
}
|
||||
|
||||
void FSourceCodeGeneratorModule::FinishExport()
|
||||
{
|
||||
TMap<UPackage*, TArray<FString>> Packages;
|
||||
|
||||
for (const auto ExportClass : ExportClasses)
|
||||
{
|
||||
Packages.FindOrAdd(ExportClass->GetPackage()).Add(*ExportClass->GetName());
|
||||
}
|
||||
|
||||
for (auto& [Key, Value] : Packages)
|
||||
{
|
||||
Value.Sort();
|
||||
|
||||
FBigStringBuilder StringBuilder;
|
||||
|
||||
StringBuilder.Append(TEXT("#pragma once\r\n\r\n"));
|
||||
|
||||
for (const auto& Name : Value)
|
||||
{
|
||||
StringBuilder.Append(GenerateInclude(*Name + BindingSuffix));
|
||||
}
|
||||
|
||||
const auto FilePath = FPaths::Combine(
|
||||
OutputPath, *FPaths::GetCleanFilename(Key->GetName()) + HeaderSuffix);
|
||||
|
||||
SaveIfChanged(FilePath, StringBuilder.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
FString FSourceCodeGeneratorModule::GetGeneratorName() const
|
||||
{
|
||||
return TEXT("UnrealCSharp Source Code Generator");
|
||||
}
|
||||
|
||||
bool FSourceCodeGeneratorModule::CanExportClass(const UClass* Class)
|
||||
{
|
||||
return !Class->HasAnyClassFlags(CLASS_Interface) &&
|
||||
IsClassTypeSupported(Class);
|
||||
}
|
||||
|
||||
bool FSourceCodeGeneratorModule::CanExportFunction(const UFunction* Function)
|
||||
{
|
||||
if (const auto OwnerClass = Function->GetOwnerClass())
|
||||
{
|
||||
if (!OwnerClass->HasAnyClassFlags(CLASS_RequiredAPI) &&
|
||||
!Function->HasAnyFunctionFlags(FUNC_RequiredAPI))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (Function->HasAnyFunctionFlags(FUNC_Delegate | FUNC_MulticastDelegate))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Function->HasAnyFunctionFlags(FUNC_Public))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Function->HasAnyFunctionFlags(FUNC_EditorOnly))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Function->HasMetaData(FName(TEXT("CustomThunk"))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (const auto ReturnProperty = Function->GetReturnProperty())
|
||||
{
|
||||
if (ReturnProperty->IsA<FMapProperty>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (TFieldIterator<FProperty> Property(Function); Property; ++Property)
|
||||
{
|
||||
if (!IsPropertyTypeSupported(*Property))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FSourceCodeGeneratorModule::CanExportProperty(const FProperty* Property)
|
||||
{
|
||||
return !Property->HasAnyPropertyFlags(CPF_EditorOnly) &&
|
||||
Property->HasAnyPropertyFlags(CPF_NativeAccessSpecifierPublic) &&
|
||||
IsPropertyTypeSupported(Property);
|
||||
}
|
||||
|
||||
void FSourceCodeGeneratorModule::ExportFunction(FBigStringBuilder& StringBuilder,
|
||||
const UClass* Class,
|
||||
const UFunction* Function)
|
||||
{
|
||||
StringBuilder.Appendf(TEXT(
|
||||
"\t\t\t\t.Function(\"%s\", BINDING_OVERLOAD(%s, &%s::%s"
|
||||
),
|
||||
*Function->GetName(),
|
||||
*GetFunctionSignature(Class, Function),
|
||||
*(Class->GetPrefixCPP() + Class->GetName()),
|
||||
*Function->GetName());
|
||||
|
||||
auto bHasParameters = false;
|
||||
|
||||
for (TFieldIterator<FProperty> Property(Function); Property; ++Property)
|
||||
{
|
||||
if (Property->HasAnyPropertyFlags(CPF_Parm) && !Property->HasAnyPropertyFlags(CPF_ReturnParm))
|
||||
{
|
||||
bHasParameters = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bHasParameters)
|
||||
{
|
||||
StringBuilder.Appendf(TEXT(
|
||||
", %s"
|
||||
),
|
||||
*GetFunctionParamName(Function));
|
||||
}
|
||||
|
||||
if (!Function->HasAnyFunctionFlags(FUNC_Static))
|
||||
{
|
||||
StringBuilder.Append(TEXT(", EFunctionInteract::Virtual"));
|
||||
}
|
||||
|
||||
if (bHasParameters)
|
||||
{
|
||||
StringBuilder.Append(GetFunctionDefaultValue(Function));
|
||||
}
|
||||
|
||||
StringBuilder.Append(TEXT("))\r\n"));
|
||||
}
|
||||
|
||||
void FSourceCodeGeneratorModule::ExportProperty(FBigStringBuilder& StringBuilder,
|
||||
const UClass* Class,
|
||||
const FProperty* Property)
|
||||
{
|
||||
StringBuilder.Appendf(TEXT(
|
||||
"\t\t\t\t.Property(\"%s\", BINDING_PROPERTY(&%s::%s))\r\n"
|
||||
),
|
||||
*Property->GetNameCPP(),
|
||||
*(Class->GetPrefixCPP() + Class->GetName()),
|
||||
*Property->GetNameCPP());
|
||||
}
|
||||
|
||||
bool FSourceCodeGeneratorModule::IsClassTypeSupported(const UClass* Class)
|
||||
{
|
||||
return !Class->HasAnyClassFlags(CLASS_Deprecated) &&
|
||||
(Class == UObject::StaticClass() ||
|
||||
Class == UClass::StaticClass() ||
|
||||
Class->HasAnyClassFlags(CLASS_RequiredAPI | CLASS_MinimalAPI));
|
||||
}
|
||||
|
||||
bool FSourceCodeGeneratorModule::IsPropertyTypeSupported(const FProperty* Property)
|
||||
{
|
||||
if (const auto ArrayProperty = CastField<FArrayProperty>(Property))
|
||||
{
|
||||
return IsPropertyTypeSupported(ArrayProperty->Inner);
|
||||
}
|
||||
|
||||
if (const auto SetProperty = CastField<FSetProperty>(Property))
|
||||
{
|
||||
return IsPropertyTypeSupported(SetProperty->ElementProp);
|
||||
}
|
||||
|
||||
if (const auto MapProperty = CastField<FMapProperty>(Property))
|
||||
{
|
||||
return IsPropertyTypeSupported(MapProperty->KeyProp) &&
|
||||
IsPropertyTypeSupported(MapProperty->ValueProp);
|
||||
}
|
||||
|
||||
if (const auto ObjectProperty = CastField<FObjectProperty>(Property))
|
||||
{
|
||||
if (!IsClassTypeSupported(ObjectProperty->PropertyClass))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto ObjectPtrProperty = CastField<FObjectPtrProperty>(Property))
|
||||
{
|
||||
if (!IsClassTypeSupported(ObjectPtrProperty->PropertyClass))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto InterfaceProperty = CastField<FInterfaceProperty>(Property))
|
||||
{
|
||||
if (InterfaceProperty->InterfaceClass == UInterface::StaticClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (Property->IsA<FEnumProperty>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Property->IsA<FByteProperty>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Property->IsA<FDelegateProperty>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Property->IsA<FMulticastDelegateProperty>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Property->IsA<FFieldPathProperty>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (const auto StructProperty = CastField<FStructProperty>(Property))
|
||||
{
|
||||
if (!StructProperty->HasNoOpConstructor())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto bIsBitField = false;
|
||||
|
||||
if (const auto BoolProperty = CastField<FBoolProperty>(Property))
|
||||
{
|
||||
if (!BoolProperty->IsNativeBool())
|
||||
{
|
||||
bIsBitField = true;
|
||||
}
|
||||
}
|
||||
|
||||
return !bIsBitField && !(Property->ArrayDim > 1);
|
||||
}
|
||||
|
||||
FString FSourceCodeGeneratorModule::GetReturnPropertySignature(const FProperty* Property)
|
||||
{
|
||||
if (Property == nullptr)
|
||||
{
|
||||
return TEXT("void");
|
||||
}
|
||||
|
||||
FString String;
|
||||
|
||||
auto bIsConst = false;
|
||||
|
||||
if (Property->HasAnyPropertyFlags(CPF_ConstParm) || Property->HasMetaData(FName(TEXT("NativeConst"))))
|
||||
{
|
||||
String.Append(TEXT("const "));
|
||||
|
||||
bIsConst = true;
|
||||
}
|
||||
|
||||
GetPropertySignature(Property, String);
|
||||
|
||||
if (Property->IsA<FStrProperty>())
|
||||
{
|
||||
if (bIsConst &&
|
||||
!Property->HasAnyPropertyFlags(CPF_ConstParm))
|
||||
{
|
||||
String.Append(TEXT("&"));
|
||||
}
|
||||
}
|
||||
else if (Property->HasAnyPropertyFlags(CPF_ReferenceParm) &&
|
||||
Property->HasAnyPropertyFlags(CPF_ConstParm))
|
||||
{
|
||||
String.Append(TEXT("&"));
|
||||
}
|
||||
|
||||
return String;
|
||||
}
|
||||
|
||||
FString FSourceCodeGeneratorModule::GetParamPropertySignature(const FProperty* Property)
|
||||
{
|
||||
if (Property == nullptr)
|
||||
{
|
||||
return TEXT("void");
|
||||
}
|
||||
|
||||
FString String;
|
||||
|
||||
auto bIsConst = false;
|
||||
|
||||
if (Property->HasAnyPropertyFlags(CPF_ConstParm) || Property->HasMetaData(FName(TEXT("NativeConst"))))
|
||||
{
|
||||
String.Append(TEXT("const "));
|
||||
|
||||
bIsConst = true;
|
||||
}
|
||||
|
||||
GetPropertySignature(Property, String);
|
||||
|
||||
if (Property->IsA<FStrProperty>())
|
||||
{
|
||||
if (bIsConst &&
|
||||
!Property->HasAnyPropertyFlags(CPF_ConstParm) ||
|
||||
Property->HasAnyPropertyFlags(CPF_OutParm))
|
||||
{
|
||||
String.Append(TEXT("&"));
|
||||
}
|
||||
}
|
||||
else if (Property->HasAnyPropertyFlags(CPF_OutParm) ||
|
||||
Property->HasAnyPropertyFlags(CPF_ReferenceParm))
|
||||
{
|
||||
String.Append(TEXT("&"));
|
||||
}
|
||||
|
||||
return String;
|
||||
}
|
||||
|
||||
FString FSourceCodeGeneratorModule::GetFunctionSignature(const UClass* Class, const UFunction* Function)
|
||||
{
|
||||
FString String;
|
||||
|
||||
String.Append(GetReturnPropertySignature(Function->GetReturnProperty()));
|
||||
|
||||
if (Function->HasAnyFunctionFlags(FUNC_Static))
|
||||
{
|
||||
String.Append(TEXT("(*)"));
|
||||
}
|
||||
else
|
||||
{
|
||||
String.Appendf(TEXT(
|
||||
"(%s::*)"
|
||||
),
|
||||
*(Class->GetPrefixCPP() + Class->GetName())
|
||||
);
|
||||
}
|
||||
|
||||
String.Append(TEXT("("));
|
||||
|
||||
auto bHasParameters = false;
|
||||
|
||||
for (TFieldIterator<FProperty> Property(Function); Property; ++Property)
|
||||
{
|
||||
if (!Property->HasAnyPropertyFlags(CPF_ReturnParm))
|
||||
{
|
||||
String.Append(GetParamPropertySignature(*Property));
|
||||
|
||||
String.Append(TEXT(", "));
|
||||
|
||||
bHasParameters = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (bHasParameters)
|
||||
{
|
||||
String.LeftInline(String.Len() - 2);
|
||||
}
|
||||
|
||||
String.Append(TEXT(")"));
|
||||
|
||||
if (Function->HasAnyFunctionFlags(FUNC_Const))
|
||||
{
|
||||
String.Append(TEXT("const"));
|
||||
}
|
||||
|
||||
return String;
|
||||
}
|
||||
|
||||
FString FSourceCodeGeneratorModule::GetFunctionParamName(const UFunction* Function)
|
||||
{
|
||||
FSmallStringBuilder Builder;
|
||||
|
||||
Builder.Append(TEXT("TArray<FString>{"));
|
||||
|
||||
auto bIsConst = false;
|
||||
|
||||
for (TFieldIterator<FProperty> Property(Function); Property; ++Property)
|
||||
{
|
||||
if (!Property->HasAnyPropertyFlags(CPF_ReturnParm))
|
||||
{
|
||||
Builder.Appendf(TEXT(
|
||||
"\"%s\", "
|
||||
),
|
||||
*Property->GetName()
|
||||
);
|
||||
|
||||
bIsConst = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (bIsConst)
|
||||
{
|
||||
Builder.RemoveSuffix(2);
|
||||
}
|
||||
|
||||
Builder.Append(TEXT("}"));
|
||||
|
||||
return Builder.ToString();
|
||||
}
|
||||
|
||||
FString FSourceCodeGeneratorModule::GetFunctionDefaultValue(const UFunction* Function)
|
||||
{
|
||||
if (!Function->HasAnyFunctionFlags(FUNC_BlueprintCallable))
|
||||
{
|
||||
return FString();
|
||||
}
|
||||
|
||||
FSmallStringBuilder Builder;
|
||||
|
||||
const auto MetaDataMap = UMetaData::GetMapForObject(Function);
|
||||
|
||||
for (TFieldIterator<FProperty> Property(Function); Property; ++Property)
|
||||
{
|
||||
if (Property->HasAnyPropertyFlags(CPF_Parm) && !Property->HasAnyPropertyFlags(CPF_ReturnParm))
|
||||
{
|
||||
if (FString Value; FindDefaultValue(MetaDataMap, *Property, Value))
|
||||
{
|
||||
Builder.Append(TEXT(", "));
|
||||
|
||||
if (Value == TEXT("NULL"))
|
||||
{
|
||||
Builder.Append(TEXT("nullptr"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Builder.Append(Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Builder.ToString();
|
||||
}
|
||||
|
||||
bool FSourceCodeGeneratorModule::FindDefaultValue(const TMap<FName, FString>* MetaDataMap, const FProperty* Param,
|
||||
FString& OutString)
|
||||
{
|
||||
if (MetaDataMap == nullptr || Param == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FName ParamName(*Param->GetName());
|
||||
|
||||
const FName CppKey(*(FString(TEXT("CPP_Default_")) + ParamName.ToString()));
|
||||
|
||||
if (!MetaDataMap->Contains(CppKey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Param->IsA<FObjectProperty>()
|
||||
|| Param->IsA<FClassPtrProperty>()
|
||||
|| Param->IsA<FObjectPtrProperty>())
|
||||
{
|
||||
OutString = TEXT("nullptr");
|
||||
}
|
||||
else
|
||||
{
|
||||
FString MetaData;
|
||||
|
||||
if (const auto CppDefaultValue = MetaDataMap->Find(CppKey))
|
||||
{
|
||||
if (!CppDefaultValue->IsEmpty())
|
||||
{
|
||||
MetaData = *CppDefaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto StructProperty = CastField<FStructProperty>(Param))
|
||||
{
|
||||
if (StructProperty->Struct == TBaseStructure<FVector>::Get())
|
||||
{
|
||||
TArray<FString> Value;
|
||||
|
||||
MetaData.ParseIntoArray(Value, TEXT(","));
|
||||
|
||||
if (Value.Num() == 3)
|
||||
{
|
||||
OutString = FString::Printf(TEXT(
|
||||
"FVector(%lf, %lf, %lf)"
|
||||
),
|
||||
TCString<TCHAR>::Atod(*Value[0]),
|
||||
TCString<TCHAR>::Atod(*Value[1]),
|
||||
TCString<TCHAR>::Atod(*Value[2])
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (StructProperty->Struct == TBaseStructure<FVector2D>::Get())
|
||||
{
|
||||
if (FVector2D Value; Value.InitFromString(MetaData))
|
||||
{
|
||||
OutString = FString::Printf(TEXT(
|
||||
"FVector2D(%lf, %lf)"
|
||||
),
|
||||
Value.X,
|
||||
Value.Y
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (StructProperty->Struct == TBaseStructure<FRotator>::Get())
|
||||
{
|
||||
TArray<FString> Value;
|
||||
|
||||
MetaData.ParseIntoArray(Value, TEXT(","));
|
||||
|
||||
if (Value.Num() == 3)
|
||||
{
|
||||
OutString = FString::Printf(TEXT(
|
||||
"FRotator(%lf, %lf, %lf)"
|
||||
),
|
||||
TCString<TCHAR>::Atod(*Value[0]),
|
||||
TCString<TCHAR>::Atod(*Value[1]),
|
||||
TCString<TCHAR>::Atod(*Value[2])
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (StructProperty->Struct == TBaseStructure<FLinearColor>::Get())
|
||||
{
|
||||
if (FLinearColor Value; Value.InitFromString(MetaData))
|
||||
{
|
||||
OutString = FString::Printf(TEXT(
|
||||
"FLinearColor(%ff, %ff, %ff, %ff)"
|
||||
),
|
||||
Value.R,
|
||||
Value.G,
|
||||
Value.B,
|
||||
Value.A
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (OutString.IsEmpty())
|
||||
{
|
||||
OutString = FString::Printf(TEXT(
|
||||
"%s%s()"
|
||||
),
|
||||
StructProperty->Struct->GetPrefixCPP(),
|
||||
*StructProperty->Struct->GetName()
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (Param->IsA<FStrProperty>())
|
||||
{
|
||||
OutString = FString::Printf(TEXT(
|
||||
"FString(\"%s\")"
|
||||
),
|
||||
*MetaData);
|
||||
}
|
||||
else if (Param->IsA<FNameProperty>())
|
||||
{
|
||||
OutString = FString::Printf(TEXT(
|
||||
"TEXT(\"%s\")"
|
||||
),
|
||||
*MetaData);
|
||||
}
|
||||
else if (Param->IsA<FTextProperty>())
|
||||
{
|
||||
static auto InvText = FString(TEXT("INVTEXT"));
|
||||
|
||||
if (MetaData.Contains(InvText))
|
||||
{
|
||||
MetaData.MidInline(InvText.Len() + 2, MetaData.Len() - InvText.Len() - 4);
|
||||
}
|
||||
|
||||
OutString = FString::Printf(TEXT(
|
||||
"INVTEXT(\"%s\")"
|
||||
),
|
||||
*MetaData
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutString = MetaData;
|
||||
}
|
||||
}
|
||||
|
||||
return !OutString.IsEmpty();
|
||||
}
|
||||
|
||||
void FSourceCodeGeneratorModule::GetPropertySignature(const FProperty* Property, FString& String)
|
||||
{
|
||||
if (const auto ArrayProperty = CastField<FArrayProperty>(Property))
|
||||
{
|
||||
String.Appendf(TEXT(
|
||||
"TArray<%s>"
|
||||
),
|
||||
*ArrayProperty->Inner->GetCPPType()
|
||||
);
|
||||
}
|
||||
else if (const auto SetProperty = CastField<FSetProperty>(Property))
|
||||
{
|
||||
String.Appendf(TEXT(
|
||||
"TSet<%s>"
|
||||
),
|
||||
*SetProperty->ElementProp->GetCPPType()
|
||||
);
|
||||
}
|
||||
else if (const auto MapProperty = CastField<FMapProperty>(Property))
|
||||
{
|
||||
String.Appendf(TEXT(
|
||||
"TMap<%s, %s>"
|
||||
),
|
||||
*MapProperty->KeyProp->GetCPPType(),
|
||||
*MapProperty->ValueProp->GetCPPType()
|
||||
);
|
||||
}
|
||||
else if (const auto InterfaceProperty = CastField<FInterfaceProperty>(Property))
|
||||
{
|
||||
String.Appendf(TEXT(
|
||||
"TScriptInterface<I%s>"
|
||||
),
|
||||
*InterfaceProperty->InterfaceClass->GetName()
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
String.Append(Property->GetCPPType());
|
||||
}
|
||||
}
|
||||
|
||||
void FSourceCodeGeneratorModule::GetDependencyClasses(const FProperty* Property, TSet<UClass*>& DependencyClasses)
|
||||
{
|
||||
if (const auto ClassProperty = CastField<FClassProperty>(Property))
|
||||
{
|
||||
if (ClassProperty->MetaClass != nullptr)
|
||||
{
|
||||
DependencyClasses.Add(ClassProperty->MetaClass);
|
||||
}
|
||||
}
|
||||
else if (const auto ObjectProperty = CastField<FObjectProperty>(Property))
|
||||
{
|
||||
if (ObjectProperty->PropertyClass != nullptr)
|
||||
{
|
||||
DependencyClasses.Add(ObjectProperty->PropertyClass);
|
||||
}
|
||||
}
|
||||
else if (const auto ObjectPtrProperty = CastField<FObjectPtrProperty>(Property))
|
||||
{
|
||||
if (ObjectPtrProperty->PropertyClass != nullptr)
|
||||
{
|
||||
DependencyClasses.Add(ObjectPtrProperty->PropertyClass);
|
||||
}
|
||||
}
|
||||
else if (const auto SoftClassProperty = CastField<FSoftClassProperty>(Property))
|
||||
{
|
||||
if (SoftClassProperty->MetaClass != nullptr)
|
||||
{
|
||||
DependencyClasses.Add(SoftClassProperty->MetaClass);
|
||||
}
|
||||
}
|
||||
else if (const auto SoftObjectProperty = CastField<FSoftObjectProperty>(Property))
|
||||
{
|
||||
if (SoftObjectProperty->PropertyClass != nullptr)
|
||||
{
|
||||
DependencyClasses.Add(SoftObjectProperty->PropertyClass);
|
||||
}
|
||||
}
|
||||
else if (const auto ArrayProperty = CastField<FArrayProperty>(Property))
|
||||
{
|
||||
if (ArrayProperty->Inner != nullptr)
|
||||
{
|
||||
GetDependencyClasses(ArrayProperty->Inner, DependencyClasses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FSourceCodeGeneratorModule::SaveIfChanged(const FString& FileName, const FString& String)
|
||||
{
|
||||
if (FPaths::FileExists(FileName))
|
||||
{
|
||||
if (FString Result; FFileHelper::LoadFileToString(Result, *FileName) && Result == String)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FFileHelper::SaveStringToFile(String, *FileName, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM);
|
||||
}
|
||||
|
||||
FString FSourceCodeGeneratorModule::GetHeaderFile(UClass* Class)
|
||||
{
|
||||
FString ModuleRelativePath = Class->GetMetaData(TEXT("ModuleRelativePath"));
|
||||
|
||||
if (ModuleRelativePath.IsEmpty())
|
||||
{
|
||||
if (Class == UInterface::StaticClass())
|
||||
{
|
||||
ModuleRelativePath = TEXT("Public/UObject/Interface.h");
|
||||
}
|
||||
else if (Class == UPackageMap::StaticClass())
|
||||
{
|
||||
ModuleRelativePath = TEXT("Public/UObject/CoreNet.h");
|
||||
}
|
||||
}
|
||||
|
||||
return FPaths::Combine(HeaderPath.FindRef(FPaths::GetCleanFilename(Class->GetPackage()->GetName())),
|
||||
ModuleRelativePath);
|
||||
}
|
||||
|
||||
FString FSourceCodeGeneratorModule::GenerateInclude(const FString& FileName)
|
||||
{
|
||||
return FString::Printf(TEXT(
|
||||
"#include \"%s\"\n"
|
||||
),
|
||||
*FileName
|
||||
);
|
||||
}
|
||||
|
||||
FString FSourceCodeGeneratorModule::GetInclude(UClass* Class)
|
||||
{
|
||||
return GenerateInclude(GetHeaderFile(Class));
|
||||
}
|
||||
|
||||
void FSourceCodeGeneratorModule::GetPlugins(const FString& InPathName, TMap<FString, FString>& Plugins)
|
||||
{
|
||||
const auto Suffix = FString(TEXT("*.uplugin"));
|
||||
|
||||
if (auto& FileManager = IFileManager::Get(); FileManager.DirectoryExists(*InPathName))
|
||||
{
|
||||
TArray<FString> FileNames;
|
||||
|
||||
FileManager.FindFilesRecursive(FileNames, *InPathName, *Suffix, true, false);
|
||||
|
||||
for (const auto& FileName : FileNames)
|
||||
{
|
||||
Plugins.Add(
|
||||
FPaths::GetCleanFilename(FileName).Left(
|
||||
FPaths::GetCleanFilename(FileName).Len() - Suffix.Len() + 1),
|
||||
FPaths::ConvertRelativePathToFull(FPaths::GetPath(FileName)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FSourceCodeGeneratorModule::GetModules(const FString& InPathName, TMap<FString, FString>& Modules)
|
||||
{
|
||||
const auto Suffix = FString(TEXT("*.Build.cs"));
|
||||
|
||||
if (auto& FileManager = IFileManager::Get(); FileManager.DirectoryExists(*InPathName))
|
||||
{
|
||||
TArray<FString> FileNames;
|
||||
|
||||
FileManager.FindFilesRecursive(FileNames, *InPathName, *Suffix, true, false);
|
||||
|
||||
for (const auto& FileName : FileNames)
|
||||
{
|
||||
Modules.Add(
|
||||
FPaths::GetCleanFilename(FileName).Left(
|
||||
FPaths::GetCleanFilename(FileName).Len() - Suffix.Len() + 1),
|
||||
FPaths::ConvertRelativePathToFull(FPaths::GetPath(FileName)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FSourceCodeGeneratorModule::GetModules(const FString& InPathName, TSet<FString>& Modules)
|
||||
{
|
||||
const auto Suffix = FString(TEXT("*.Build.cs"));
|
||||
|
||||
if (auto& FileManager = IFileManager::Get(); FileManager.DirectoryExists(*InPathName))
|
||||
{
|
||||
TArray<FString> FileNames;
|
||||
|
||||
FileManager.FindFilesRecursive(FileNames, *InPathName, *Suffix, true, false);
|
||||
|
||||
for (const auto& FileName : FileNames)
|
||||
{
|
||||
Modules.Add(
|
||||
FPaths::GetCleanFilename(FileName).Left(
|
||||
FPaths::GetCleanFilename(FileName).Len() - Suffix.Len() + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FSourceCodeGeneratorModule, SourceCodeGenerator)
|
||||
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "IScriptGeneratorPluginInterface.h"
|
||||
|
||||
class FSourceCodeGeneratorModule : public IScriptGeneratorPluginInterface
|
||||
{
|
||||
private:
|
||||
static constexpr auto LargeInitialBufferSizes = 256 * 1024;
|
||||
|
||||
static constexpr auto SmallInitialBufferSizes = 1 * 1024;
|
||||
|
||||
typedef TStringBuilder<LargeInitialBufferSizes> FBigStringBuilder;
|
||||
|
||||
typedef TStringBuilder<SmallInitialBufferSizes> FSmallStringBuilder;
|
||||
|
||||
public:
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
public:
|
||||
/** IScriptGeneratorPluginInterface implementation */
|
||||
virtual FString GetGeneratedCodeModuleName() const override;
|
||||
|
||||
virtual bool SupportsTarget(const FString& TargetName) const override;
|
||||
|
||||
virtual bool ShouldExportClassesForModule(const FString& ModuleName, EBuildModuleType::Type ModuleType,
|
||||
const FString& ModuleGeneratedIncludeDirectory) const override;
|
||||
|
||||
virtual void Initialize(const FString& RootLocalPath, const FString& RootBuildPath,
|
||||
const FString& OutputDirectory, const FString& IncludeBase) override;
|
||||
|
||||
virtual void ExportClass(UClass* Class, const FString& SourceHeaderFilename,
|
||||
const FString& GeneratedHeaderFilename, bool bHasChanged) override;
|
||||
|
||||
virtual void FinishExport() override;
|
||||
|
||||
virtual FString GetGeneratorName() const override;
|
||||
|
||||
private:
|
||||
static bool CanExportClass(const UClass* Class);
|
||||
|
||||
static bool CanExportFunction(const UFunction* Function);
|
||||
|
||||
static bool CanExportProperty(const FProperty* Property);
|
||||
|
||||
static void ExportFunction(FBigStringBuilder& StringBuilder, const UClass* Class, const UFunction* Function);
|
||||
|
||||
static void ExportProperty(FBigStringBuilder& StringBuilder, const UClass* Class, const FProperty* Property);
|
||||
|
||||
static bool IsClassTypeSupported(const UClass* Class);
|
||||
|
||||
static bool IsPropertyTypeSupported(const FProperty* Property);
|
||||
|
||||
static FString GetReturnPropertySignature(const FProperty* Property);
|
||||
|
||||
static FString GetParamPropertySignature(const FProperty* Property);
|
||||
|
||||
static FString GetFunctionSignature(const UClass* Class, const UFunction* Function);
|
||||
|
||||
static FString GetFunctionParamName(const UFunction* Function);
|
||||
|
||||
static FString GetFunctionDefaultValue(const UFunction* Function);
|
||||
|
||||
static bool FindDefaultValue(const TMap<FName, FString>* MetaMap, const FProperty* Param, FString& String);
|
||||
|
||||
static void GetPropertySignature(const FProperty* Property, FString& String);
|
||||
|
||||
static void GetDependencyClasses(const FProperty* Property, TSet<UClass*>& DependencyClasses);
|
||||
|
||||
static void SaveIfChanged(const FString& FileName, const FString& String);
|
||||
|
||||
FString GetHeaderFile(UClass* Class);
|
||||
|
||||
static FString GenerateInclude(const FString& FileName);
|
||||
|
||||
FString GetInclude(UClass* Class);
|
||||
|
||||
static void GetPlugins(const FString& InPathName, TMap<FString, FString>& Plugins);
|
||||
|
||||
static void GetModules(const FString& InPathName, TMap<FString, FString>& Modules);
|
||||
|
||||
static void GetModules(const FString& InPathName, TSet<FString>& Modules);
|
||||
|
||||
private:
|
||||
FString OutputPath;
|
||||
|
||||
TArray<FString> ExportModules;
|
||||
|
||||
TSet<FString> Project;
|
||||
|
||||
TMap<FString, FString> HeaderPath;
|
||||
|
||||
TArray<UClass*> ExportClasses;
|
||||
|
||||
const FString BindingSuffix = FString(TEXT(".binding.inl"));
|
||||
|
||||
const FString HeaderSuffix = FString(TEXT(".header.inl"));
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
using UnrealBuildTool;
|
||||
|
||||
namespace UnrealBuildTool.Rules
|
||||
{
|
||||
public class SourceCodeGenerator : ModuleRules
|
||||
{
|
||||
public SourceCodeGenerator(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
bEnforceIWYU = false;
|
||||
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Programs/UnrealHeaderTool/Public",
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PrivateIncludePaths.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"SourceCodeGenerator/Private",
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
}
|
||||
);
|
||||
|
||||
PublicDefinitions.Add("HACK_HEADER_GENERATOR=1");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user