Copying //UE4/Dev-Enterprise to //UE4/Dev-Main (Source: //UE4/Dev-Enterprise @ 4071915)

#lockdown Nick.Penwarden
#rb none

============================
  MAJOR FEATURES & CHANGES
============================

Change 4060527 by Anousack.Kitisa

	Added support for importing FBX user properties as metadata on StaticMesh when importing FBX.
	Added support for exporting StaticMesh metadata as FBX user properties when exporting StaticMesh to FBX.

	#jira UESP-567

Change 4060835 by Jamie.Dale

	Added assign method to Python exposed structs

	This lets you assign the value of one struct instance onto another instance (rather than copy the pointer in Python). It also accepts anything that casts to the destination struct.

Change 4060838 by Jamie.Dale

	Include unary operator function tooltips in doc string

Change 4060843 by Jamie.Dale

	Fixed PythonizeValue including deprecated properties in the init function for a struct

Change 4060908 by Jamie.Dale

	Fixed some name conflicts in generated Python glue

Change 4061065 by Jamie.Dale

	Stubbed struct return values are now default constructed

Change 4061205 by David.Hibbitts

	Added blueprint functions to create a message bus source, for use in projects where the Editor UI is not available or is impractical.
	Added a blueprint function to get available subject names for the LiveLink Client
	Added a RemoveSource method to ILiveLinkClient
	Added a GetSubjectNames method to ILiveLinkClient
	Fixed a crash when RequestShutdown was called on a MessageBusSource after the HeartbeatManager had already been shut down.

Change 4061421 by Patrick.Boutot

	[AJA] Warn the user if he requested the key and the backbuffer is not setup properly.
	#jira UE-58614

Change 4061620 by Jamie.Dale

	Made the Sphinx config a template so we can inject the current engine version into it

Change 4062578 by Jamie.Dale

	Optimized Python stub and doc gen file writes

	 - Files are now only written when they've changed.
	 - We now only remove files that are stale.
	 - No requests to generate stub and doc files are queued before the first Tick.

Change 4062634 by Jamie.Dale

	No longer export FDateTime defaults to struct __init__ as they can be non-deterministic

Change 4064275 by Jamie.Dale

	Added callbacks for when Python is initialized and shutdown so that external modules can hook-in appropriately

Change 4064613 by James.McNatton

	Change to initialization for FVirtualCameraWaypoint and FVirtualCameraSettingsPreset to remove non-deterministic constructors and a few resulting cleanup items

Change 4064878 by Patrick.Boutot

	Add timecode provider plugin to capture from the Audio jack.

Change 4064910 by Patrick.Boutot

	[AJA]
	Add AjaTimecodeProvider that provider the timecode from a SDI input source.

Change 4067451 by Jamie.Dale

	Added command line options to enable all plugins, optionally excluding certain plugins

Change 4067489 by Simon.Tourangeau

	Support for DX12 quad buffer stereo rendering

Change 4068640 by Patrick.Boutot

	Add a state to CustomTimeStep. Show the state of the CustomTimeStep in "stat fps".

Change 4069147 by Patrick.Boutot

	Move Mediasmith console to Engine. Renamed to TimecodeSynchronizer.

Change 4071727 by Matt.Hoffman

	Initial pass at exposing Sequencer's Render to Movie functionality to Python. All settings that can be adjusted via the UI can be set from Python and renders can be invoked for both in-editor capture as well as new process capture. A basic API is provided which enables querying if a render in progress and canceling an in progress one.

	#jira UESP-541

[CL 4071957 by JeanMichel Dignard in Main branch]
This commit is contained in:
JeanMichel Dignard
2018-05-14 17:38:22 -04:00
parent f4d3b0eaa1
commit 6ab73c6457
237 changed files with 6070 additions and 1808 deletions

View File

@@ -1,12 +1,10 @@
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "PyFileWriter.h"
#include "Misc/FileHelper.h"
#include "PyGenUtil.h"
#if WITH_PYTHON
FPyFileWriter::FPyFileWriter()
: Indentation(0)
{
@@ -88,8 +86,7 @@ void FPyFileWriter::DecreaseIndent(const int32 InCount)
bool FPyFileWriter::SaveFile(const TCHAR* InFilename)
{
return FFileHelper::SaveStringToFile(FileContents, InFilename, FFileHelper::EEncodingOptions::ForceUTF8);
return PyGenUtil::SaveGeneratedTextFile(InFilename, FileContents);
}
#endif // WITH_PYTHON

View File

@@ -9,6 +9,7 @@
#include "PyWrapperStruct.h"
#include "Internationalization/BreakIterator.h"
#include "Misc/Paths.h"
#include "Misc/FileHelper.h"
#include "UObject/Class.h"
#include "UObject/Package.h"
#include "UObject/EnumProperty.h"
@@ -1807,17 +1808,21 @@ FString PythonizeTooltip(const FString& InTooltip, const FPythonizeTooltipContex
return PythonizedTooltip;
}
void PythonizeStructValueImpl(const UScriptStruct* InStruct, const void* InStructValue, const bool InIncludeUnrealNamespace, const bool InUseStrictTyping, FString& OutPythonDefaultValue);
void PythonizeStructValueImpl(const UScriptStruct* InStruct, const void* InStructValue, const uint32 InFlags, FString& OutPythonDefaultValue);
void PythonizeValueImpl(const UProperty* InProp, const void* InPropValue, const bool InIncludeUnrealNamespace, const bool InUseStrictTyping, FString& OutPythonDefaultValue)
void PythonizeValueImpl(const UProperty* InProp, const void* InPropValue, const uint32 InFlags, FString& OutPythonDefaultValue)
{
static const bool bIsForDocString = false;
const TCHAR* UnrealNamespace = InIncludeUnrealNamespace ? TEXT("unreal.") : TEXT("");
const bool bIncludeUnrealNamespace = !!(InFlags & EPythonizeValueFlags::IncludeUnrealNamespace);
const bool bUseStrictTyping = !!(InFlags & EPythonizeValueFlags::UseStrictTyping);
const TCHAR* UnrealNamespace = bIncludeUnrealNamespace ? TEXT("unreal.") : TEXT("");
if (InProp->ArrayDim > 1)
{
OutPythonDefaultValue += InUseStrictTyping
? FString::Printf(TEXT("%sFixedArray.cast(%s, ["), UnrealNamespace, *GetPropertyTypePythonName(InProp, InIncludeUnrealNamespace, bIsForDocString))
OutPythonDefaultValue += bUseStrictTyping
? FString::Printf(TEXT("%sFixedArray.cast(%s, ["), UnrealNamespace, *GetPropertyTypePythonName(InProp, bIncludeUnrealNamespace, bIsForDocString))
: TEXT("[");
}
for (int32 ArrIndex = 0; ArrIndex < InProp->ArrayDim; ++ArrIndex)
@@ -1855,7 +1860,7 @@ void PythonizeValueImpl(const UProperty* InProp, const void* InPropValue, const
else if (const UNameProperty* NameProp = Cast<const UNameProperty>(InProp))
{
const FString NameStrValue = NameProp->GetPropertyValue(PropArrValue).ToString();
OutPythonDefaultValue += InUseStrictTyping
OutPythonDefaultValue += bUseStrictTyping
? FString::Printf(TEXT("%sName(\"%s\")"), UnrealNamespace, *NameStrValue)
: FString::Printf(TEXT("\"%s\""), *NameStrValue);
}
@@ -1863,7 +1868,7 @@ void PythonizeValueImpl(const UProperty* InProp, const void* InPropValue, const
{
const FString* TextStrValue = FTextInspector::GetSourceString(TextProp->GetPropertyValue(PropArrValue));
check(TextStrValue);
OutPythonDefaultValue += InUseStrictTyping
OutPythonDefaultValue += bUseStrictTyping
? FString::Printf(TEXT("%sText(\"%s\")"), UnrealNamespace, **TextStrValue)
: FString::Printf(TEXT("\"%s\""), **TextStrValue);
}
@@ -1877,7 +1882,7 @@ void PythonizeValueImpl(const UProperty* InProp, const void* InPropValue, const
}
else if (const UStructProperty* StructProp = Cast<const UStructProperty>(InProp))
{
PythonizeStructValueImpl(StructProp->Struct, PropArrValue, InIncludeUnrealNamespace, InUseStrictTyping, OutPythonDefaultValue);
PythonizeStructValueImpl(StructProp->Struct, PropArrValue, InFlags, OutPythonDefaultValue);
}
else if (const UDelegateProperty* DelegateProp = Cast<const UDelegateProperty>(InProp))
{
@@ -1889,8 +1894,8 @@ void PythonizeValueImpl(const UProperty* InProp, const void* InPropValue, const
}
else if (const UArrayProperty* ArrayProperty = Cast<const UArrayProperty>(InProp))
{
OutPythonDefaultValue += InUseStrictTyping
? FString::Printf(TEXT("%sArray.cast(%s, ["), UnrealNamespace, *GetPropertyTypePythonName(ArrayProperty->Inner, InIncludeUnrealNamespace, bIsForDocString))
OutPythonDefaultValue += bUseStrictTyping
? FString::Printf(TEXT("%sArray.cast(%s, ["), UnrealNamespace, *GetPropertyTypePythonName(ArrayProperty->Inner, bIncludeUnrealNamespace, bIsForDocString))
: TEXT("[");
{
FScriptArrayHelper ScriptArrayHelper(ArrayProperty, PropArrValue);
@@ -1901,17 +1906,17 @@ void PythonizeValueImpl(const UProperty* InProp, const void* InPropValue, const
{
OutPythonDefaultValue += TEXT(", ");
}
PythonizeValueImpl(ArrayProperty->Inner, ScriptArrayHelper.GetRawPtr(ElementIndex), InIncludeUnrealNamespace, InUseStrictTyping, OutPythonDefaultValue);
PythonizeValueImpl(ArrayProperty->Inner, ScriptArrayHelper.GetRawPtr(ElementIndex), InFlags, OutPythonDefaultValue);
}
}
OutPythonDefaultValue += InUseStrictTyping
OutPythonDefaultValue += bUseStrictTyping
? TEXT("])")
: TEXT("]");
}
else if (const USetProperty* SetProperty = Cast<const USetProperty>(InProp))
{
OutPythonDefaultValue += InUseStrictTyping
? FString::Printf(TEXT("%sSet.cast(%s, ["), UnrealNamespace, *GetPropertyTypePythonName(SetProperty->ElementProp, InIncludeUnrealNamespace, bIsForDocString))
OutPythonDefaultValue += bUseStrictTyping
? FString::Printf(TEXT("%sSet.cast(%s, ["), UnrealNamespace, *GetPropertyTypePythonName(SetProperty->ElementProp, bIncludeUnrealNamespace, bIsForDocString))
: TEXT("[");
{
FScriptSetHelper ScriptSetHelper(SetProperty, PropArrValue);
@@ -1923,18 +1928,18 @@ void PythonizeValueImpl(const UProperty* InProp, const void* InPropValue, const
{
OutPythonDefaultValue += TEXT(", ");
}
PythonizeValueImpl(ScriptSetHelper.GetElementProperty(), ScriptSetHelper.GetElementPtr(SparseElementIndex), InIncludeUnrealNamespace, InUseStrictTyping, OutPythonDefaultValue);
PythonizeValueImpl(ScriptSetHelper.GetElementProperty(), ScriptSetHelper.GetElementPtr(SparseElementIndex), InFlags, OutPythonDefaultValue);
}
}
}
OutPythonDefaultValue += InUseStrictTyping
OutPythonDefaultValue += bUseStrictTyping
? TEXT("])")
: TEXT("]");
}
else if (const UMapProperty* MapProperty = Cast<const UMapProperty>(InProp))
{
OutPythonDefaultValue += InUseStrictTyping
? FString::Printf(TEXT("%sMap.cast(%s, %s, {"), UnrealNamespace, *GetPropertyTypePythonName(MapProperty->KeyProp, InIncludeUnrealNamespace, bIsForDocString), *GetPropertyTypePythonName(MapProperty->ValueProp, InIncludeUnrealNamespace, bIsForDocString))
OutPythonDefaultValue += bUseStrictTyping
? FString::Printf(TEXT("%sMap.cast(%s, %s, {"), UnrealNamespace, *GetPropertyTypePythonName(MapProperty->KeyProp, bIncludeUnrealNamespace, bIsForDocString), *GetPropertyTypePythonName(MapProperty->ValueProp, bIncludeUnrealNamespace, bIsForDocString))
: TEXT("{");
{
FScriptMapHelper ScriptMapHelper(MapProperty, PropArrValue);
@@ -1946,13 +1951,13 @@ void PythonizeValueImpl(const UProperty* InProp, const void* InPropValue, const
{
OutPythonDefaultValue += TEXT(", ");
}
PythonizeValueImpl(ScriptMapHelper.GetKeyProperty(), ScriptMapHelper.GetKeyPtr(SparseElementIndex), InIncludeUnrealNamespace, InUseStrictTyping, OutPythonDefaultValue);
PythonizeValueImpl(ScriptMapHelper.GetKeyProperty(), ScriptMapHelper.GetKeyPtr(SparseElementIndex), InFlags, OutPythonDefaultValue);
OutPythonDefaultValue += TEXT(": ");
PythonizeValueImpl(ScriptMapHelper.GetValueProperty(), ScriptMapHelper.GetValuePtr(SparseElementIndex), InIncludeUnrealNamespace, InUseStrictTyping, OutPythonDefaultValue);
PythonizeValueImpl(ScriptMapHelper.GetValueProperty(), ScriptMapHelper.GetValuePtr(SparseElementIndex), InFlags, OutPythonDefaultValue);
}
}
}
OutPythonDefaultValue += InUseStrictTyping
OutPythonDefaultValue += bUseStrictTyping
? TEXT("})")
: TEXT("}");
}
@@ -1964,15 +1969,20 @@ void PythonizeValueImpl(const UProperty* InProp, const void* InPropValue, const
}
if (InProp->ArrayDim > 1)
{
OutPythonDefaultValue += InUseStrictTyping
OutPythonDefaultValue += bUseStrictTyping
? TEXT("])")
: TEXT("]");
}
}
void PythonizeStructValueImpl(const UScriptStruct* InStruct, const void* InStructValue, const bool InIncludeUnrealNamespace, const bool InUseStrictTyping, FString& OutPythonDefaultValue)
void PythonizeStructValueImpl(const UScriptStruct* InStruct, const void* InStructValue, const uint32 InFlags, FString& OutPythonDefaultValue)
{
const TCHAR* UnrealNamespace = InIncludeUnrealNamespace ? TEXT("unreal.") : TEXT("");
const bool bIncludeUnrealNamespace = !!(InFlags & EPythonizeValueFlags::IncludeUnrealNamespace);
const bool bUseStrictTyping = !!(InFlags & EPythonizeValueFlags::UseStrictTyping);
const bool bDefaultConstructStructs = !!(InFlags & EPythonizeValueFlags::DefaultConstructStructs);
const bool bDefaultConstructDateTime = !!(InFlags & EPythonizeValueFlags::DefaultConstructDateTime);
const TCHAR* UnrealNamespace = bIncludeUnrealNamespace ? TEXT("unreal.") : TEXT("");
// Note: We deliberately don't use any FPyWrapperStruct functionality here as this function may be called as part of generating the wrapped type
@@ -1986,88 +1996,91 @@ void PythonizeStructValueImpl(const UScriptStruct* InStruct, const void* InStruc
return nullptr;
};
const UFunction* MakeFunc = FindMakeBreakFunction(HasNativeMakeMetaDataKey);
const UFunction* BreakFunc = FindMakeBreakFunction(HasNativeBreakMetaDataKey);
// If the struct has a make function, we assume the output of the break function matches the input of the make function
OutPythonDefaultValue += InUseStrictTyping
OutPythonDefaultValue += bUseStrictTyping
? FString::Printf(TEXT("%s%s("), UnrealNamespace, *GetStructPythonName(InStruct))
: TEXT("[");
if (MakeFunc && BreakFunc)
if (!bDefaultConstructStructs && (!bDefaultConstructDateTime || !InStruct->IsChildOf(TBaseStructure<FDateTime>::Get())))
{
UClass* Class = BreakFunc->GetOwnerClass();
UObject* Obj = Class->GetDefaultObject();
const UFunction* MakeFunc = FindMakeBreakFunction(HasNativeMakeMetaDataKey);
const UFunction* BreakFunc = FindMakeBreakFunction(HasNativeBreakMetaDataKey);
FGeneratedWrappedFunction BreakFuncDef;
BreakFuncDef.SetFunction(BreakFunc, FGeneratedWrappedFunction::SFF_ExtractParameters);
// Python can only support 255 parameters, so if we have more than that for this struct just use the default constructor
if (BreakFuncDef.OutputParams.Num() <= 255)
if (MakeFunc && BreakFunc)
{
// Call the break function using the instance we were given
FStructOnScope FuncParams(BreakFuncDef.Func);
if (BreakFuncDef.InputParams.Num() == 1 && Cast<UStructProperty>(BreakFuncDef.InputParams[0].ParamProp) && InStruct->IsChildOf(CastChecked<UStructProperty>(BreakFuncDef.InputParams[0].ParamProp)->Struct))
{
// Copy the given instance as the 'self' argument
const FGeneratedWrappedMethodParameter& SelfParam = BreakFuncDef.InputParams[0];
void* SelfArgInstance = SelfParam.ParamProp->ContainerPtrToValuePtr<void>(FuncParams.GetStructMemory());
CastChecked<UStructProperty>(SelfParam.ParamProp)->Struct->CopyScriptStruct(SelfArgInstance, InStructValue);
}
PyUtil::InvokeFunctionCall(Obj, BreakFuncDef.Func, FuncParams.GetStructMemory(), TEXT("pythonize default struct value"));
PyErr_Clear(); // Clear any errors in case InvokeFunctionCall failed
UClass* Class = BreakFunc->GetOwnerClass();
UObject* Obj = Class->GetDefaultObject();
// Extract the output argument values as defaults for the struct
for (int32 OuputParamIndex = 0; OuputParamIndex < BreakFuncDef.OutputParams.Num(); ++OuputParamIndex)
FGeneratedWrappedFunction BreakFuncDef;
BreakFuncDef.SetFunction(BreakFunc, FGeneratedWrappedFunction::SFF_ExtractParameters);
// Python can only support 255 parameters, so if we have more than that for this struct just use the default constructor
if (BreakFuncDef.OutputParams.Num() <= 255)
{
const FGeneratedWrappedMethodParameter& OutputParam = BreakFuncDef.OutputParams[OuputParamIndex];
if (OuputParamIndex > 0)
// Call the break function using the instance we were given
FStructOnScope FuncParams(BreakFuncDef.Func);
if (BreakFuncDef.InputParams.Num() == 1 && Cast<UStructProperty>(BreakFuncDef.InputParams[0].ParamProp) && InStruct->IsChildOf(CastChecked<UStructProperty>(BreakFuncDef.InputParams[0].ParamProp)->Struct))
{
OutPythonDefaultValue += TEXT(", ");
// Copy the given instance as the 'self' argument
const FGeneratedWrappedMethodParameter& SelfParam = BreakFuncDef.InputParams[0];
void* SelfArgInstance = SelfParam.ParamProp->ContainerPtrToValuePtr<void>(FuncParams.GetStructMemory());
CastChecked<UStructProperty>(SelfParam.ParamProp)->Struct->CopyScriptStruct(SelfArgInstance, InStructValue);
}
PythonizeValueImpl(OutputParam.ParamProp, OutputParam.ParamProp->ContainerPtrToValuePtr<void>(FuncParams.GetStructMemory()), InIncludeUnrealNamespace, InUseStrictTyping, OutPythonDefaultValue);
PyUtil::InvokeFunctionCall(Obj, BreakFuncDef.Func, FuncParams.GetStructMemory(), TEXT("pythonize default struct value"));
PyErr_Clear(); // Clear any errors in case InvokeFunctionCall failed
// Extract the output argument values as defaults for the struct
for (int32 OuputParamIndex = 0; OuputParamIndex < BreakFuncDef.OutputParams.Num(); ++OuputParamIndex)
{
const FGeneratedWrappedMethodParameter& OutputParam = BreakFuncDef.OutputParams[OuputParamIndex];
if (OuputParamIndex > 0)
{
OutPythonDefaultValue += TEXT(", ");
}
PythonizeValueImpl(OutputParam.ParamProp, OutputParam.ParamProp->ContainerPtrToValuePtr<void>(FuncParams.GetStructMemory()), InFlags, OutPythonDefaultValue);
}
}
}
else
{
int32 ExportedPropertyCount = 0;
FString StructInitParamsStr;
for (TFieldIterator<const UProperty> PropIt(InStruct, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt)
{
const UProperty* Prop = *PropIt;
if (ShouldExportProperty(Prop) && !IsDeprecatedProperty(Prop))
{
if (ExportedPropertyCount++ > 0)
{
StructInitParamsStr += TEXT(", ");
}
PythonizeValueImpl(Prop, Prop->ContainerPtrToValuePtr<void>(InStructValue), InFlags, StructInitParamsStr);
}
}
// Python can only support 255 parameters, so if we have more than that for this struct just use the default constructor
if (ExportedPropertyCount <= 255)
{
OutPythonDefaultValue += StructInitParamsStr;
}
}
}
else
{
int32 ExportedPropertyCount = 0;
FString StructInitParamsStr;
for (TFieldIterator<const UProperty> PropIt(InStruct, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt)
{
const UProperty* Prop = *PropIt;
if (ShouldExportProperty(Prop))
{
if (ExportedPropertyCount++ > 0)
{
StructInitParamsStr += TEXT(", ");
}
PythonizeValueImpl(Prop, Prop->ContainerPtrToValuePtr<void>(InStructValue), InIncludeUnrealNamespace, InUseStrictTyping, StructInitParamsStr);
}
}
// Python can only support 255 parameters, so if we have more than that for this struct just use the default constructor
if (ExportedPropertyCount <= 255)
{
OutPythonDefaultValue += StructInitParamsStr;
}
}
OutPythonDefaultValue += InUseStrictTyping
OutPythonDefaultValue += bUseStrictTyping
? TEXT(")")
: TEXT("]");
}
FString PythonizeValue(const UProperty* InProp, const void* InPropValue, const bool InIncludeUnrealNamespace, const bool InUseStrictTyping)
FString PythonizeValue(const UProperty* InProp, const void* InPropValue, const uint32 InFlags)
{
FString PythonValue;
PythonizeValueImpl(InProp, InPropValue, InIncludeUnrealNamespace, InUseStrictTyping, PythonValue);
PythonizeValueImpl(InProp, InPropValue, InFlags, PythonValue);
return PythonValue;
}
FString PythonizeDefaultValue(const UProperty* InProp, const FString& InDefaultValue, const bool InIncludeUnrealNamespace, const bool InUseStrictTyping)
FString PythonizeDefaultValue(const UProperty* InProp, const FString& InDefaultValue, const uint32 InFlags)
{
PyUtil::FPropValueOnScope PropValue(InProp);
PyUtil::ImportDefaultValue(InProp, PropValue.GetValue(), InDefaultValue);
return PythonizeValue(InProp, PropValue.GetValue(), InIncludeUnrealNamespace, InUseStrictTyping);
return PythonizeValue(InProp, PropValue.GetValue(), InFlags);
}
FString GetFieldModule(const UField* InField)
@@ -2581,6 +2594,34 @@ void AppendCppSourceInformationDocString(const UField* InOwnerType, FString& Out
OutStr += LINE_TERMINATOR;
}
bool SaveGeneratedTextFile(const TCHAR* InFilename, const FString& InFileContents, const bool InForceWrite)
{
bool bWriteFile = InForceWrite;
if (!bWriteFile)
{
FString CurrentFileContents;
if (FFileHelper::LoadFileToString(CurrentFileContents, InFilename))
{
// Only write the file if the contents differ
bWriteFile = !InFileContents.Equals(CurrentFileContents, ESearchCase::CaseSensitive);
}
else
{
// Failed to load the file, assume it's missing so write it
bWriteFile = true;
}
}
if (bWriteFile)
{
return FFileHelper::SaveStringToFile(InFileContents, InFilename, FFileHelper::EEncodingOptions::ForceUTF8);
}
// File up-to-date, return success
return true;
}
}
#endif // WITH_PYTHON

View File

@@ -840,6 +840,19 @@ namespace PyGenUtil
Upper,
};
/** Flags controlling the behavior of PythonizeValue */
namespace EPythonizeValueFlags
{
enum EFlags
{
None = 0,
IncludeUnrealNamespace = 1<<0,
UseStrictTyping = 1<<1,
DefaultConstructStructs = 1<<2,
DefaultConstructDateTime = 1<<3,
};
};
/** Context information passed to PythonizeTooltip */
struct FPythonizeTooltipContext
{
@@ -958,10 +971,10 @@ namespace PyGenUtil
FString PythonizeTooltip(const FString& InTooltip, const FPythonizeTooltipContext& InContext = FPythonizeTooltipContext());
/** Given a property and its value, convert it into something that could be used in a Python script */
FString PythonizeValue(const UProperty* InProp, const void* InPropValue, const bool InIncludeUnrealNamespace = false, const bool InUseStrictTyping = false);
FString PythonizeValue(const UProperty* InProp, const void* InPropValue, const uint32 InFlags = EPythonizeValueFlags::None);
/** Given a property and its default value, convert it into something that could be used in a Python script */
FString PythonizeDefaultValue(const UProperty* InProp, const FString& InDefaultValue, const bool InIncludeUnrealNamespace = false, const bool InUseStrictTyping = false);
FString PythonizeDefaultValue(const UProperty* InProp, const FString& InDefaultValue, const uint32 InFlags = EPythonizeValueFlags::None);
/** Get the native module the given field belongs to */
FString GetFieldModule(const UField* InField);
@@ -1040,6 +1053,9 @@ namespace PyGenUtil
/** Append the doc string for the C++ source information of the given type to the given string */
void AppendCppSourceInformationDocString(const UField* InOwnerType, FString& OutStr);
/** Save a generated text file to disk as UTF-8 (only writes the file if the contents differs, unless forced) */
bool SaveGeneratedTextFile(const TCHAR* InFilename, const FString& InFileContents, const bool InForceWrite = false);
}
#endif // WITH_PYTHON

View File

@@ -2,13 +2,44 @@
#include "PyOnlineDocsWriter.h"
#include "PyUtil.h"
#include "PyGenUtil.h"
#include "HAL/FileManager.h"
#include "Logging/MessageLog.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Runtime/Launch/Resources/Version.h"
#if WITH_PYTHON
class FPyDeleteUnreferencedFilesVisitor : public IPlatformFile::FDirectoryVisitor
{
public:
void ReferenceFile(const FString& InFilename)
{
ReferencedFiles.Add(FPaths::ConvertRelativePathToFull(InFilename));
}
bool IsReferencedFile(const FString& InFilename) const
{
return ReferencedFiles.Contains(FPaths::ConvertRelativePathToFull(InFilename));
}
virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override
{
if (!bIsDirectory && !IsReferencedFile(FilenameOrDirectory))
{
IFileManager::Get().Delete(FilenameOrDirectory, false, true, true);
}
return true;
}
private:
/** Set of referenced files (absolute paths) */
TSet<FString> ReferencedFiles;
};
void FPyOnlineDocsModule::AccumulateFunction(const TCHAR* InFunctionName)
{
FunctionNames.Add(InFunctionName);
@@ -46,6 +77,29 @@ FString FPyOnlineDocsWriter::GetTemplatePath() const
return GetSourcePath() / TEXT("_templates");
}
void FPyOnlineDocsWriter::GenerateConfigFile()
{
// Load up conf.py template
const FString ConfigTemplatePath = GetTemplatePath() / TEXT("conf.py");
FString ConfigTemplate;
if (!FFileHelper::LoadFileToString(ConfigTemplate, *ConfigTemplatePath))
{
UE_LOG(LogPython, Warning, TEXT("Documentation generation template file '%s' failed to load!"), *ConfigTemplatePath);
return;
}
// Replace {{Version}} with the actual version number
FString ConfigText = ConfigTemplate;
ConfigText.ReplaceInline(TEXT("{{Version}}"), VERSION_STRINGIFY(ENGINE_MAJOR_VERSION) TEXT(".") VERSION_STRINGIFY(ENGINE_MINOR_VERSION), ESearchCase::CaseSensitive);
// Save out config file
const FString ConfigPath = GetSourcePath() / TEXT("conf.py");
if (!PyGenUtil::SaveGeneratedTextFile(*ConfigPath, ConfigText))
{
UE_LOG(LogPython, Warning, TEXT("Documentation generation file '%s' failed to write!"), *ConfigPath);
}
}
void FPyOnlineDocsWriter::GenerateIndexFile()
{
// Load up index.rst template
@@ -53,7 +107,7 @@ void FPyOnlineDocsWriter::GenerateIndexFile()
FString IndexTemplate;
if (!FFileHelper::LoadFileToString(IndexTemplate, *IndexTemplatePath))
{
UE_LOG(LogPython, Warning, TEXT("Documentation generation template file '%s' was not found!"), *IndexTemplatePath);
UE_LOG(LogPython, Warning, TEXT("Documentation generation template file '%s' failed to load!"), *IndexTemplatePath);
return;
}
@@ -109,25 +163,28 @@ void FPyOnlineDocsWriter::GenerateIndexFile()
// Save out index file
const FString IndexPath = GetSourcePath() / TEXT("index.rst");
FFileHelper::SaveStringToFile(IndexText, *IndexPath, FFileHelper::EEncodingOptions::ForceUTF8);
if (!PyGenUtil::SaveGeneratedTextFile(*IndexPath, IndexText))
{
UE_LOG(LogPython, Warning, TEXT("Documentation generation file '%s' failed to write!"), *IndexPath);
}
}
void FPyOnlineDocsWriter::GenerateModuleFiles()
{
// Erase any previous files
const FString ModuleSourcePath = GetSourcePath() / TEXT("module");
IFileManager::Get().DeleteDirectory(*ModuleSourcePath, false, true);
// Load up Module.rst template
const FString ModuleTemplatePath = GetTemplatePath() / TEXT("Module.rst");
FString ModuleTemplate;
if (!FFileHelper::LoadFileToString(ModuleTemplate, *ModuleTemplatePath))
{
UE_LOG(LogPython, Warning, TEXT("Documentation generation template file '%s' was not found!"), *ModuleTemplatePath);
UE_LOG(LogPython, Warning, TEXT("Documentation generation template file '%s' failed to load!"), *ModuleTemplatePath);
return;
}
// Keep track of referenced files so we can delete any stale ones
FPyDeleteUnreferencedFilesVisitor DeleteUnreferencedFilesVisitor;
// Create page for each module
const FString ModuleSourcePath = GetSourcePath() / TEXT("module");
for (const TSharedRef<FPyOnlineDocsModule>& Module : Modules)
{
FString ModuleFunctions;
@@ -144,26 +201,36 @@ void FPyOnlineDocsWriter::GenerateModuleFiles()
// Write out module file
const FString ModulePath = ModuleSourcePath / Module->Name + TEXT(".rst");
FFileHelper::SaveStringToFile(ModuleText, *ModulePath, FFileHelper::EEncodingOptions::ForceUTF8);
if (PyGenUtil::SaveGeneratedTextFile(*ModulePath, ModuleText))
{
DeleteUnreferencedFilesVisitor.ReferenceFile(ModulePath);
}
else
{
UE_LOG(LogPython, Warning, TEXT("Documentation generation file '%s' failed to write!"), *ModulePath);
}
}
// Remove any stale files
IFileManager::Get().IterateDirectory(*ModuleSourcePath, DeleteUnreferencedFilesVisitor);
}
void FPyOnlineDocsWriter::GenerateClassFiles()
{
// Erase any previous files
const FString ClassSourcePath = GetSourcePath() / TEXT("class");
IFileManager::Get().DeleteDirectory(*ClassSourcePath, false, true);
// Load up Class.rst template
const FString ClassTemplatePath = GetTemplatePath() / TEXT("Class.rst");
FString ClassTemplate;
if (!FFileHelper::LoadFileToString(ClassTemplate, *ClassTemplatePath))
{
UE_LOG(LogPython, Warning, TEXT("Documentation generation template file '%s' was not found!"), *ClassTemplatePath);
UE_LOG(LogPython, Warning, TEXT("Documentation generation template file '%s' failed to load!"), *ClassTemplatePath);
return;
}
// Keep track of referenced files so we can delete any stale ones
FPyDeleteUnreferencedFilesVisitor DeleteUnreferencedFilesVisitor;
// Create page for each class in each section
const FString ClassSourcePath = GetSourcePath() / TEXT("class");
for (const TSharedRef<FPyOnlineDocsSection>& Section : Sections)
{
for (const FString& TypeName : Section->TypeNames)
@@ -174,9 +241,19 @@ void FPyOnlineDocsWriter::GenerateClassFiles()
// Write out class file
const FString ClassPath = ClassSourcePath / TypeName + TEXT(".rst");
FFileHelper::SaveStringToFile(ClassText, *ClassPath, FFileHelper::EEncodingOptions::ForceUTF8);
if (PyGenUtil::SaveGeneratedTextFile(*ClassPath, ClassText))
{
DeleteUnreferencedFilesVisitor.ReferenceFile(*ClassPath);
}
else
{
UE_LOG(LogPython, Warning, TEXT("Documentation generation file '%s' failed to write!"), *ClassPath);
}
}
}
// Remove any stale files
IFileManager::Get().IterateDirectory(*ClassSourcePath, DeleteUnreferencedFilesVisitor);
}
void FPyOnlineDocsWriter::GenerateFiles(const FString& InPythonStubPath)
@@ -184,9 +261,24 @@ void FPyOnlineDocsWriter::GenerateFiles(const FString& InPythonStubPath)
UE_LOG(LogPython, Log, TEXT("Generating Python API online docs used by Sphinx to generate static HTML..."));
// Copy generated unreal module stub file to PythonScriptPlugin/SphinxDocs/modules
const FString MoldulesPath = GetSphinxDocsPath() / TEXT("modules") / FPaths::GetCleanFilename(InPythonStubPath);
IFileManager::Get().Copy(*MoldulesPath, *InPythonStubPath);
{
const FString PythonStubDestPath = GetSphinxDocsPath() / TEXT("modules") / FPaths::GetCleanFilename(InPythonStubPath);
FString SourceFileContents;
if (FFileHelper::LoadFileToString(SourceFileContents, *InPythonStubPath))
{
if (!PyGenUtil::SaveGeneratedTextFile(*PythonStubDestPath, SourceFileContents))
{
UE_LOG(LogPython, Warning, TEXT("Documentation generation file '%s' failed to write!"), *PythonStubDestPath);
}
}
else
{
UE_LOG(LogPython, Warning, TEXT("Documentation generation file '%s' failed to load!"), *InPythonStubPath);
}
}
GenerateConfigFile();
GenerateIndexFile();
GenerateModuleFiles();
GenerateClassFiles();

View File

@@ -79,6 +79,9 @@ public:
/** Get the directory for the Sphinx template files. */
FString GetTemplatePath() const;
/** Create Python config for Sphinx based on template. */
void GenerateConfigFile();
/** Create index reStructuredText file for Sphinx based on template. */
void GenerateIndexFile();

View File

@@ -954,6 +954,35 @@ PyTypeObject InitializePyWrapperStructType()
return (PyObject*)FPyWrapperStructFactory::Get().CreateInstance(InSelf->ScriptStruct, InSelf->StructInstance, FPyWrapperOwnerContext(), EPyConversionMethod::Copy);
}
static PyObject* Assign(FPyWrapperStruct* InSelf, PyObject* InArgs)
{
if (!FPyWrapperStruct::ValidateInternalState(InSelf))
{
return nullptr;
}
PyObject* PyObj = nullptr;
if (!PyArg_ParseTuple(InArgs, "O:assign", &PyObj))
{
return nullptr;
}
check(PyObj);
FPyWrapperStructPtr PyStruct = FPyWrapperStructPtr::StealReference(FPyWrapperStruct::CastPyObject(PyObj, Py_TYPE(InSelf)));
if (!PyStruct)
{
PyUtil::SetPythonError(PyExc_TypeError, InSelf, *FString::Printf(TEXT("Cannot cast type '%s' to '%s'"), *PyUtil::GetFriendlyTypename(PyObj), *PyUtil::GetFriendlyTypename(InSelf)));
return nullptr;
}
if (PyStruct && ensureAlways(PyStruct->ScriptStruct->IsChildOf(InSelf->ScriptStruct)))
{
InSelf->ScriptStruct->CopyScriptStruct(InSelf->StructInstance, PyStruct->StructInstance);
}
Py_RETURN_NONE;
}
static PyObject* ToTuple(FPyWrapperStruct* InSelf)
{
return FPyWrapperStruct::BreakStruct(InSelf);
@@ -1079,6 +1108,7 @@ PyTypeObject InitializePyWrapperStructType()
{ "static_struct", PyCFunctionCast(&FMethods::StaticStruct), METH_NOARGS | METH_CLASS, "X.static_struct() -> Struct -- get the Unreal struct of this type" },
{ "__copy__", PyCFunctionCast(&FMethods::Copy), METH_NOARGS, "x.__copy__() -> struct -- copy this Unreal struct" },
{ "copy", PyCFunctionCast(&FMethods::Copy), METH_NOARGS, "x.copy() -> struct -- copy this Unreal struct" },
{ "assign", PyCFunctionCast(&FMethods::Assign), METH_VARARGS, "x.assign(object) -> None -- assign the value of this Unreal struct to value of the given object" },
{ "to_tuple", PyCFunctionCast(&FMethods::ToTuple), METH_NOARGS, "x.to_tuple() -> tuple -- break this Unreal struct into a tuple of its properties" },
{ "get_editor_property", PyCFunctionCast(&FMethods::GetEditorProperty), METH_VARARGS | METH_KEYWORDS, "x.get_editor_property(name) -> object -- get the value of any property visible to the editor" },
{ "set_editor_property", PyCFunctionCast(&FMethods::SetEditorProperty), METH_VARARGS | METH_KEYWORDS, "x.set_editor_property(name, value) -> None -- set the value of any property visible to the editor, ensuring that the pre/post change notifications are called" },

View File

@@ -1936,8 +1936,6 @@ void FPyWrapperTypeRegistry::GatherWrappedTypesForPropertyReferences(const UProp
void FPyWrapperTypeRegistry::GenerateStubCodeForWrappedTypes() const
{
// todo: delete the PythonStub directory?
FPyFileWriter PythonScript;
TUniquePtr<FPyOnlineDocsWriter> OnlineDocsWriter;
@@ -2122,8 +2120,8 @@ void FPyWrapperTypeRegistry::GenerateStubCodeForWrappedType(PyTypeObject* PyType
return TEXT("None");
}
// We use strict typing for return values to aid auto-complete
static const bool bUseStrictTyping = true;
// We use strict typing for return values to aid auto-complete (we also only care about the type and not the value, so structs can be default constructed)
static const uint32 PythonizeValueFlags = PyGenUtil::EPythonizeValueFlags::UseStrictTyping | PyGenUtil::EPythonizeValueFlags::DefaultConstructStructs;
// If we have multiple return values and the main return value is a bool, skip it (to mimic PyGenUtils::PackReturnValues)
int32 ReturnPropIndex = 0;
@@ -2137,7 +2135,7 @@ void FPyWrapperTypeRegistry::GenerateStubCodeForWrappedType(PyTypeObject* PyType
if (NumPropertiesToPack == 1)
{
const PyGenUtil::FGeneratedWrappedMethodParameter& ReturnParam = InOutputParams[ReturnPropIndex];
return PyGenUtil::PythonizeValue(ReturnParam.ParamProp, ReturnParam.ParamProp->ContainerPtrToValuePtr<void>(InBaseParamsAddr), /*IncludeUnrealNamespace*/false, bUseStrictTyping);
return PyGenUtil::PythonizeValue(ReturnParam.ParamProp, ReturnParam.ParamProp->ContainerPtrToValuePtr<void>(InBaseParamsAddr), PythonizeValueFlags);
}
else
{
@@ -2149,7 +2147,7 @@ void FPyWrapperTypeRegistry::GenerateStubCodeForWrappedType(PyTypeObject* PyType
FunctionReturnStr += TEXT(", ");
}
const PyGenUtil::FGeneratedWrappedMethodParameter& ReturnParam = InOutputParams[ReturnPropIndex];
FunctionReturnStr += PyGenUtil::PythonizeValue(ReturnParam.ParamProp, ReturnParam.ParamProp->ContainerPtrToValuePtr<void>(InBaseParamsAddr), /*IncludeUnrealNamespace*/false, bUseStrictTyping);
FunctionReturnStr += PyGenUtil::PythonizeValue(ReturnParam.ParamProp, ReturnParam.ParamProp->ContainerPtrToValuePtr<void>(InBaseParamsAddr), PythonizeValueFlags);
}
FunctionReturnStr += TEXT(")");
return FunctionReturnStr;
@@ -2266,15 +2264,35 @@ void FPyWrapperTypeRegistry::GenerateStubCodeForWrappedType(PyTypeObject* PyType
auto ExportGeneratedGetSet = [&ExportGetSet](const PyGenUtil::FGeneratedWrappedGetSet& InGetSet)
{
// We use strict typing for return values to aid auto-complete
static const bool bUseStrictTyping = true;
const FString GetReturnValue = PyGenUtil::PythonizeDefaultValue(InGetSet.Prop.Prop, FString(), /*IncludeUnrealNamespace*/false, bUseStrictTyping);
// We use strict typing for return values to aid auto-complete (we also only care about the type and not the value, so structs can be default constructed)
static const uint32 PythonizeValueFlags = PyGenUtil::EPythonizeValueFlags::UseStrictTyping | PyGenUtil::EPythonizeValueFlags::DefaultConstructStructs;
const FString GetReturnValue = PyGenUtil::PythonizeDefaultValue(InGetSet.Prop.Prop, FString(), PythonizeValueFlags);
const bool bIsReadOnly = InGetSet.Prop.Prop->HasAnyPropertyFlags(CPF_BlueprintReadOnly | CPF_EditConst);
ExportGetSet(UTF8_TO_TCHAR(InGetSet.GetSetName.GetData()), UTF8_TO_TCHAR(InGetSet.GetSetDoc.GetData()), *GetReturnValue, bIsReadOnly);
};
auto ExportGeneratedOperator = [&OutPythonScript](const PyGenUtil::FGeneratedWrappedOperatorStack& InOpStack, const PyGenUtil::FGeneratedWrappedOperatorSignature& InOpSignature)
{
auto AppendFunctionTooltip = [](const UFunction* InFunc, const TCHAR* InIdentation, FString& OutStr)
{
const FString FuncTooltip = PyGenUtil::GetFieldTooltip(InFunc);
TArray<FString> FuncTooltipLines;
FuncTooltip.ParseIntoArrayLines(FuncTooltipLines, /*bCullEmpty*/false);
bool bMultipleLines = false;
for (const FString& FuncTooltipLine : FuncTooltipLines)
{
if (bMultipleLines)
{
OutStr += LINE_TERMINATOR;
OutStr += InIdentation;
}
bMultipleLines = true;
OutStr += FuncTooltipLine;
}
};
FString OpDocString;
if (InOpSignature.OtherType != PyGenUtil::FGeneratedWrappedOperatorSignature::EType::None)
{
@@ -2283,28 +2301,17 @@ void FPyWrapperTypeRegistry::GenerateStubCodeForWrappedType(PyTypeObject* PyType
{
if (OpFunc.OtherParam.ParamProp)
{
const FString FuncTooltip = PyGenUtil::GetFieldTooltip(OpFunc.Func);
TArray<FString> FuncTooltipLines;
FuncTooltip.ParseIntoArrayLines(FuncTooltipLines, /*bCullEmpty*/false);
OpDocString += LINE_TERMINATOR TEXT("- ``"); // add as a list and code style
OpDocString += PyGenUtil::GetPropertyTypePythonName(OpFunc.OtherParam.ParamProp);
OpDocString += TEXT("`` ");
bool bMultipleLines = false;
for (const FString& FuncTooltipLine : FuncTooltipLines)
{
if (bMultipleLines)
{
OpDocString += LINE_TERMINATOR TEXT(" ");
}
bMultipleLines = true;
OpDocString += FuncTooltipLine;
}
AppendFunctionTooltip(OpFunc.Func, TEXT(" "), OpDocString);
}
}
}
else if (InOpStack.Funcs.Num() > 0)
{
AppendFunctionTooltip(InOpStack.Funcs[0].Func, TEXT(""), OpDocString);
}
OutPythonScript.WriteLine(FString::Printf(TEXT("def %s(self%s):"), InOpSignature.PyFuncName, (InOpSignature.OtherType == PyGenUtil::FGeneratedWrappedOperatorSignature::EType::None ? TEXT("") : TEXT(", other"))));
OutPythonScript.IncreaseIndent();
@@ -2338,6 +2345,9 @@ void FPyWrapperTypeRegistry::GenerateStubCodeForWrappedType(PyTypeObject* PyType
TSharedPtr<const FPyWrapperStructMetaData> StructMetaData = StaticCastSharedPtr<FPyWrapperStructMetaData>(GeneratedTypeData->MetaData);
check(StructMetaData.IsValid());
// Don't export FDateTime values for struct __init__ as they can be non-deterministic
static const uint32 PythonizeValueFlags = PyGenUtil::EPythonizeValueFlags::DefaultConstructDateTime;
// Python can only support 255 parameters, so if we have more than that for this struct just use the generic __init__ function
FString InitParamsStr;
if (StructMetaData->MakeFunc.Func)
@@ -2351,7 +2361,7 @@ void FPyWrapperTypeRegistry::GenerateStubCodeForWrappedType(PyTypeObject* PyType
if (InitParam.ParamDefaultValue.IsSet())
{
InitParamsStr += TEXT("=");
InitParamsStr += PyGenUtil::PythonizeDefaultValue(InitParam.ParamProp, InitParam.ParamDefaultValue.GetValue());
InitParamsStr += PyGenUtil::PythonizeDefaultValue(InitParam.ParamProp, InitParam.ParamDefaultValue.GetValue(), PythonizeValueFlags);
}
}
}
@@ -2367,7 +2377,7 @@ void FPyWrapperTypeRegistry::GenerateStubCodeForWrappedType(PyTypeObject* PyType
if (InitParam.ParamDefaultValue.IsSet())
{
InitParamsStr += TEXT("=");
InitParamsStr += PyGenUtil::PythonizeValue(InitParam.ParamProp, InitParam.ParamProp->ContainerPtrToValuePtr<void>(StructData.GetStructMemory()));
InitParamsStr += PyGenUtil::PythonizeValue(InitParam.ParamProp, InitParam.ParamProp->ContainerPtrToValuePtr<void>(StructData.GetStructMemory()), PythonizeValueFlags);
}
}
}

View File

@@ -361,6 +361,16 @@ bool FPythonScriptPlugin::ExecPythonCommand(const TCHAR* InPythonCommand)
#endif // WITH_PYTHON
}
FSimpleMulticastDelegate& FPythonScriptPlugin::OnPythonInitialized()
{
return OnPythonInitializedDelegate;
}
FSimpleMulticastDelegate& FPythonScriptPlugin::OnPythonShutdown()
{
return OnPythonShutdownDelegate;
}
void FPythonScriptPlugin::StartupModule()
{
#if WITH_PYTHON
@@ -517,11 +527,6 @@ void FPythonScriptPlugin::InitializePython()
// Initialize the wrapped types
FPyWrapperTypeRegistry::Get().GenerateWrappedTypes();
#if WITH_EDITOR
// Register to generate stub code on first tick after all modules loaded.
ModulesChangedDelayedNotify();
#endif // WITH_EDITOR
// Initialize the tick handler
TickHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([this](float DeltaTime)
{
@@ -529,6 +534,9 @@ void FPythonScriptPlugin::InitializePython()
return true;
}));
}
// Notify any external listeners
OnPythonInitializedDelegate.Broadcast();
}
void FPythonScriptPlugin::ShutdownPython()
@@ -538,10 +546,13 @@ void FPythonScriptPlugin::ShutdownPython()
return;
}
// Notify any external listeners
OnPythonShutdownDelegate.Broadcast();
FTicker::GetCoreTicker().RemoveTicker(TickHandle);
if (ModuleDelayedHandle.IsValid())
{
FTicker::GetCoreTicker().RemoveTicker(TickHandle);
FTicker::GetCoreTicker().RemoveTicker(ModuleDelayedHandle);
}
FPyWrapperTypeRegistry::Get().OnModuleDirtied().RemoveAll(this);
@@ -568,31 +579,22 @@ void FPythonScriptPlugin::ShutdownPython()
bHasTicked = false;
}
void FPythonScriptPlugin::ModulesChangedDelayedNotify()
void FPythonScriptPlugin::RequestStubCodeGeneration()
{
// Delay 3 seconds before notifying.
float Delay = 3.0f;
// Ignore requests made before the fist Tick
if (!bHasTicked)
{
// Waiting for first notify yet?
if (ModuleDelayedHandle.IsValid())
{
// First notify is set up and has not yet happened
return;
}
// Notify on first tick
Delay = 0.0f;
return;
}
else
// Delay 2 seconds before generating as this may be triggered by loading several modules at once
static const float Delay = 2.0f;
// If there is an existing pending notification, remove it so that it can be reset
if (ModuleDelayedHandle.IsValid())
{
// If there is an existing pending notification, remove it so that it can be reset
if (ModuleDelayedHandle.IsValid())
{
FTicker::GetCoreTicker().RemoveTicker(ModuleDelayedHandle);
ModuleDelayedHandle.Reset();
}
FTicker::GetCoreTicker().RemoveTicker(ModuleDelayedHandle);
ModuleDelayedHandle.Reset();
}
// Set new tick
@@ -603,7 +605,7 @@ void FPythonScriptPlugin::ModulesChangedDelayedNotify()
ModuleDelayedHandle.Reset();
// Call the event now that the delay has passed.
OnModulesChangedDelayedNotify();
GenerateStubCode();
// Don't reschedule to run again.
return false;
@@ -611,6 +613,15 @@ void FPythonScriptPlugin::ModulesChangedDelayedNotify()
Delay);
}
void FPythonScriptPlugin::GenerateStubCode()
{
if (GetDefault<UPythonScriptPluginSettings>()->bDeveloperMode)
{
// Generate stub code if developer mode enabled
FPyWrapperTypeRegistry::Get().GenerateStubCodeForWrappedTypes();
}
}
void FPythonScriptPlugin::Tick(const float InDeltaTime)
{
// If this is our first Tick, handle any post-init logic that should happen once the engine is fully initialized
@@ -636,6 +647,11 @@ void FPythonScriptPlugin::Tick(const float InDeltaTime)
{
HandlePythonExecCommand(*StartupScript);
}
#if WITH_EDITOR
// Register to generate stub code after a short delay
RequestStubCodeGeneration();
#endif // WITH_EDITOR
}
FPyWrapperTypeReinstancer::Get().ProcessPending();
@@ -877,16 +893,16 @@ void FPythonScriptPlugin::OnModulesChanged(FName InModuleName, EModuleChangeReas
case EModuleChangeReason::ModuleLoaded:
FPyWrapperTypeRegistry::Get().GenerateWrappedTypesForModule(InModuleName);
#if WITH_EDITOR
// Register to generate stub code after delay or reset delay.
ModulesChangedDelayedNotify();
// Register to generate stub code after a short delay
RequestStubCodeGeneration();
#endif // WITH_EDITOR
break;
case EModuleChangeReason::ModuleUnloaded:
FPyWrapperTypeRegistry::Get().OrphanWrappedTypesForModule(InModuleName);
#if WITH_EDITOR
// Register to generate stub code after delay or reset delay.
ModulesChangedDelayedNotify();
// Register to generate stub code after a short delay
RequestStubCodeGeneration();
#endif // WITH_EDITOR
break;
@@ -895,15 +911,6 @@ void FPythonScriptPlugin::OnModulesChanged(FName InModuleName, EModuleChangeReas
}
}
void FPythonScriptPlugin::OnModulesChangedDelayedNotify()
{
if (GetDefault<UPythonScriptPluginSettings>()->bDeveloperMode)
{
// Generate stub code if developer mode enabled
FPyWrapperTypeRegistry::Get().GenerateStubCodeForWrappedTypes();
}
}
void FPythonScriptPlugin::OnContentPathMounted(const FString& InAssetPath, const FString& InFilesystemPath)
{
FPyScopedGIL GIL;

View File

@@ -64,6 +64,8 @@ public:
//~ IPythonScriptPlugin interface
virtual bool IsPythonAvailable() const override;
virtual bool ExecPythonCommand(const TCHAR* InPythonCommand) override;
virtual FSimpleMulticastDelegate& OnPythonInitialized() override;
virtual FSimpleMulticastDelegate& OnPythonShutdown() override;
//~ IModuleInterface interface
virtual void StartupModule() override;
@@ -95,7 +97,9 @@ private:
void ShutdownPython();
void ModulesChangedDelayedNotify();
void RequestStubCodeGeneration();
void GenerateStubCode();
void Tick(const float InDeltaTime);
@@ -103,8 +107,6 @@ private:
void OnModulesChanged(FName InModuleName, EModuleChangeReason InModuleChangeReason);
void OnModulesChangedDelayedNotify();
void OnContentPathMounted(const FString& InAssetPath, const FString& InFilesystemPath);
void OnContentPathDismounted(const FString& InAssetPath, const FString& InFilesystemPath);
@@ -129,5 +131,6 @@ private:
bool bHasTicked;
#endif // WITH_PYTHON
FSimpleMulticastDelegate OnPythonInitializedDelegate;
FSimpleMulticastDelegate OnPythonShutdownDelegate;
};

View File

@@ -27,4 +27,14 @@ public:
* @return true if the command ran successfully, false if there were errors (the output log will show the errors).
*/
virtual bool ExecPythonCommand(const TCHAR* InPythonCommand) = 0;
/**
* Delegate called after Python has been initialized.
*/
virtual FSimpleMulticastDelegate& OnPythonInitialized() = 0;
/**
* Delegate called before Python is shutdown.
*/
virtual FSimpleMulticastDelegate& OnPythonShutdown() = 0;
};