Massive refactoring in FormatMessage and LuaCallNode
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"files.autoSave": "afterDelay",
|
||||||
"typescript.tsc.autoDetect": "off",
|
"typescript.tsc.autoDetect": "off",
|
||||||
"lldb.dereferencePointers": false,
|
"lldb.dereferencePointers": false,
|
||||||
"npm.autoDetect": "off",
|
"npm.autoDetect": "off",
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
|
||||||
#include "FormatDataLibrary.h"
|
#include "FormatDataLibrary.h"
|
||||||
|
#include "Editor.h"
|
||||||
|
#include "EdGraphSchema_K2.h"
|
||||||
|
#include "Internationalization/TextFormatter.h"
|
||||||
#include "Layout/Geometry.h"
|
#include "Layout/Geometry.h"
|
||||||
#include "Widgets/Layout/Anchors.h"
|
#include "Widgets/Layout/Anchors.h"
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
@@ -49,6 +52,54 @@ void UlxFormatDataLibrary::ScanForConverters()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UFunction* UlxFormatDataLibrary::GetConverterForPinType(const UEdGraphSchema_K2 *Schema, const FEdGraphPinType& PinType, bool AllowWild)
|
||||||
|
{
|
||||||
|
// Special case: wildcard pins are unconnected pins.
|
||||||
|
if ((PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard) && AllowWild)
|
||||||
|
{
|
||||||
|
return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataBlank));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan the cached converter list for a matching type.
|
||||||
|
UlxFormatDataLibrary* FormatDataLib = GEditor->GetEditorSubsystem<UlxFormatDataLibrary>();
|
||||||
|
for (UFunction* Function : FormatDataLib->Converters)
|
||||||
|
{
|
||||||
|
FProperty* ValueProperty = Function->FindPropertyByName(TEXT("AutoConvertedValue"));
|
||||||
|
FEdGraphPinType ValuePinType;
|
||||||
|
if (!Schema->ConvertPropertyToPinType(ValueProperty, ValuePinType)) continue;
|
||||||
|
if (!Schema->ArePinTypesEquivalent(PinType, ValuePinType)) continue;
|
||||||
|
return Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A general handler for Enums.
|
||||||
|
if ((PinType.PinCategory == UEdGraphSchema_K2::PC_Byte) && (nullptr != Cast<const UEnum>(PinType.PinSubCategoryObject)))
|
||||||
|
{
|
||||||
|
return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataEnum));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A case for subclasses of 'Object' which are not exactly 'Object'.
|
||||||
|
if (PinType.PinCategory == UEdGraphSchema_K2::PC_Object)
|
||||||
|
{
|
||||||
|
return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ELogVerbosity::Type UlxFormatDataLibrary::ConvertElxFormatLogVerbosity(ElxFormatLogVerbosity Verbosity) {
|
||||||
|
switch (Verbosity) {
|
||||||
|
case ElxFormatLogVerbosity::Error: return ELogVerbosity::Error;
|
||||||
|
case ElxFormatLogVerbosity::Warning: return ELogVerbosity::Warning;
|
||||||
|
case ElxFormatLogVerbosity::Display: return ELogVerbosity::Display;
|
||||||
|
case ElxFormatLogVerbosity::Log: return ELogVerbosity::Log;
|
||||||
|
case ElxFormatLogVerbosity::ThrottledDisplay: return ELogVerbosity::Display;
|
||||||
|
case ElxFormatLogVerbosity::ThrottledLog: return ELogVerbosity::Log;
|
||||||
|
case ElxFormatLogVerbosity::Verbose: return ELogVerbosity::Verbose;
|
||||||
|
case ElxFormatLogVerbosity::VeryVerbose: return ELogVerbosity::VeryVerbose;
|
||||||
|
case ElxFormatLogVerbosity::Fatal: return ELogVerbosity::Fatal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataBool(bool AutoConvertedValue, const FString &Name)
|
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataBool(bool AutoConvertedValue, const FString &Name)
|
||||||
{
|
{
|
||||||
FFormatArgumentData Result;
|
FFormatArgumentData Result;
|
||||||
@@ -248,3 +299,55 @@ FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataEnum(uint8 Value, co
|
|||||||
}
|
}
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UlxFormatDataLibrary::FormatLogMessageInternal(UObject *Context, ElxFormatLogVerbosity Verbosity, const FString &InPattern, TArray<FFormatArgumentData> InArgs)
|
||||||
|
{
|
||||||
|
// For throttled verbosity levels, suppress repeated messages with the
|
||||||
|
// same format pattern. We key on the blueprint name + format pattern,
|
||||||
|
// and allow at most one message per second per key.
|
||||||
|
//
|
||||||
|
if ((Verbosity == ElxFormatLogVerbosity::ThrottledDisplay) || (Verbosity == ElxFormatLogVerbosity::ThrottledLog))
|
||||||
|
{
|
||||||
|
static TMap<FString, double> LastLogTime;
|
||||||
|
double Now = FPlatformTime::Seconds();
|
||||||
|
FString Key = Context->GetClass()->GetName() + TEXT("::") + InPattern;
|
||||||
|
double &Last = LastLogTime.FindOrAdd(Key, 0.0);
|
||||||
|
if (Now - Last < 1.0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Last = Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the formatted string.
|
||||||
|
//
|
||||||
|
FText InPatternText(FText::FromString(InPattern));
|
||||||
|
FText Message = FTextFormatter::Format(MoveTemp(InPatternText), MoveTemp(InArgs), false, false);
|
||||||
|
FString MessageString = Message.ToString();
|
||||||
|
|
||||||
|
// Get the blueprint name.
|
||||||
|
//
|
||||||
|
// Normally, the log function expects you to pass in a filename, and a log
|
||||||
|
// category name. We use the blueprint name for both.
|
||||||
|
//
|
||||||
|
// Using the blueprint name as a log category name is not technically
|
||||||
|
// correct. However, there is no correct way to create log categories
|
||||||
|
// from inside of blueprints. Doing it this way at least produces a reasonable
|
||||||
|
// message inside the log. What doesn't work correctly is the log message
|
||||||
|
// suppression system. Ie, console commands like 'log <category> verbose'
|
||||||
|
// don't have any effect here. The design of the log message suppression
|
||||||
|
// system is such that there just is no reasonable way to hook into it from
|
||||||
|
// inside of blueprints.
|
||||||
|
//
|
||||||
|
FString BlueprintNameString = Context->GetClass()->GetName();
|
||||||
|
auto BlueprintNameAnsi = StringCast<ANSICHAR>(*BlueprintNameString);
|
||||||
|
FLogCategoryName BlueprintNameLogCategory(Context->GetClass()->GetFName());
|
||||||
|
|
||||||
|
// Output to Log
|
||||||
|
//
|
||||||
|
ELogVerbosity::Type VerbosityValue = ConvertElxFormatLogVerbosity(Verbosity);
|
||||||
|
if (VerbosityValue <= ELogVerbosity::COMPILED_IN_MINIMUM_VERBOSITY)
|
||||||
|
{
|
||||||
|
FMsg::Logf(BlueprintNameAnsi.Get(), 0, BlueprintNameLogCategory, VerbosityValue, TEXT("%s"), *MessageString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,6 +36,52 @@
|
|||||||
|
|
||||||
#include "FormatDataLibrary.generated.h"
|
#include "FormatDataLibrary.generated.h"
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// ElxFormatLogVerbosity
|
||||||
|
//
|
||||||
|
// Controls the ELogVerbosity of the UE_LOG directive inside
|
||||||
|
// FormatLogMessage. Also controls the throttling of log
|
||||||
|
// messages.
|
||||||
|
//
|
||||||
|
// Fatal is deliberately placed at the end so that the
|
||||||
|
// editor defaults to Error (value 0) when the dropdown is
|
||||||
|
// uninitialized. The numeric values don't match
|
||||||
|
// ELogVerbosity, so a conversion function is needed.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class ElxFormatLogVerbosity : uint8 {
|
||||||
|
|
||||||
|
/** Prints an error to the console and log file. The editor collects and reports errors. */
|
||||||
|
Error,
|
||||||
|
|
||||||
|
/** Prints a warning to the console and log file. The editor collects and reports warnings. */
|
||||||
|
Warning,
|
||||||
|
|
||||||
|
/** Prints a message to the console and log file. */
|
||||||
|
Display,
|
||||||
|
|
||||||
|
/** Prints a message to the log file, but not to the console. */
|
||||||
|
Log,
|
||||||
|
|
||||||
|
/** Like Display, but suppresses repeated messages with the same format pattern (at most once per second). */
|
||||||
|
ThrottledDisplay,
|
||||||
|
|
||||||
|
/** Like Log, but suppresses repeated messages with the same format pattern (at most once per second). */
|
||||||
|
ThrottledLog,
|
||||||
|
|
||||||
|
/** Prints a message to the log file only if Verbose logging is enabled for the given category. */
|
||||||
|
Verbose,
|
||||||
|
|
||||||
|
/** Prints a message to the log file only if VeryVerbose logging is enabled. */
|
||||||
|
VeryVerbose,
|
||||||
|
|
||||||
|
/** Prints a fatal error to the console and log file, then crashes (this crashes the editor too). */
|
||||||
|
Fatal,
|
||||||
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// UlxFormatDataLibrary
|
// UlxFormatDataLibrary
|
||||||
@@ -51,13 +97,24 @@ class UlxFormatDataLibrary : public UEditorSubsystem
|
|||||||
public:
|
public:
|
||||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||||
|
|
||||||
// Get all converter functions (functions with an
|
// Given a pin type, find a converter function that can turn
|
||||||
// "AutoConvertedValue" parameter) found across all
|
// that type into an FFormatArgumentData. If AllowWild is true,
|
||||||
// loaded classes.
|
// unconnected wildcard pins will use the Blank converter.
|
||||||
|
// Returns nullptr if no converter is found.
|
||||||
//
|
//
|
||||||
const TArray<UFunction*>& GetConverters() const { return Converters; }
|
static UFunction* GetConverterForPinType(const UEdGraphSchema_K2 *Schema, const FEdGraphPinType& PinType, bool AllowWild);
|
||||||
|
|
||||||
|
// Format a message using FTextFormatter::Format, and send
|
||||||
|
// it to UE_LOG. The Context object's name is used as the
|
||||||
|
// log category. Meant to be used internally by the Format
|
||||||
|
// Log Message K2Node.
|
||||||
|
//
|
||||||
|
UFUNCTION(BlueprintCallable, meta=(WorldContext = "Context", BlueprintInternalUseOnly = "true"))
|
||||||
|
static void FormatLogMessageInternal(UObject *Context, ElxFormatLogVerbosity Verbosity, const FString &InPattern, TArray<FFormatArgumentData> InArgs);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static ELogVerbosity::Type ConvertElxFormatLogVerbosity(ElxFormatLogVerbosity Verbosity);
|
||||||
|
|
||||||
// Scan all loaded classes for converter functions.
|
// Scan all loaded classes for converter functions.
|
||||||
//
|
//
|
||||||
void ScanForConverters();
|
void ScanForConverters();
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
|
|
||||||
#include "FormatMessage.h"
|
#include "FormatMessage.h"
|
||||||
#include "Editor.h"
|
|
||||||
|
|
||||||
#include "Internationalization/TextFormatter.h"
|
#include "Internationalization/TextFormatter.h"
|
||||||
#include "BlueprintActionDatabaseRegistrar.h"
|
#include "BlueprintActionDatabaseRegistrar.h"
|
||||||
@@ -44,136 +43,75 @@
|
|||||||
// All argument pins will have Names that start with "A:"
|
// All argument pins will have Names that start with "A:"
|
||||||
|
|
||||||
|
|
||||||
static const FName VerbosityPinName(TEXT("Verbosity"));
|
const FName UK2Node_FormatMessage::VerbosityPinName(TEXT("Verbosity"));
|
||||||
static bool IsVerbosityPin(const UEdGraphPin *Pin) {
|
const FName UK2Node_FormatMessage::FormatPinName(TEXT("Format"));
|
||||||
return (Pin->PinName == VerbosityPinName);
|
const FName UK2Node_FormatMessage::ResultPinName(TEXT("Result"));
|
||||||
}
|
|
||||||
|
|
||||||
static const FName FormatPinName(TEXT("Format"));
|
|
||||||
static bool IsFormatPin(const UEdGraphPin *Pin) {
|
|
||||||
return (Pin->PinName == FormatPinName);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const FName ResultPinName(TEXT("Result"));
|
void UK2Node_FormatMessage::ReconstructNode()
|
||||||
static bool IsResultPin(const UEdGraphPin *Pin) {
|
{
|
||||||
return (Pin->PinName == ResultPinName);
|
// Save the value of the Format Pin before it gets reconstructed.
|
||||||
}
|
UEdGraphPin* FormatPin = FindPin(FormatPinName);
|
||||||
|
if (FormatPin != nullptr)
|
||||||
|
{
|
||||||
|
FormatPattern = FormatPin->DefaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Super::ReconstructNode();
|
||||||
|
}
|
||||||
|
|
||||||
void UK2Node_FormatMessage::AllocateDefaultPins()
|
void UK2Node_FormatMessage::AllocateDefaultPins()
|
||||||
{
|
{
|
||||||
|
Pins.Reset();
|
||||||
Super::AllocateDefaultPins();
|
Super::AllocateDefaultPins();
|
||||||
CreateCorrectPins();
|
|
||||||
}
|
|
||||||
|
|
||||||
void UK2Node_FormatMessage::CreateCorrectPins()
|
// Parse the format pattern to extract argument names.
|
||||||
{
|
// Note that we use the saved value of the format pattern,
|
||||||
if (FindPin(UEdGraphSchema_K2::PN_Execute) == nullptr) {
|
// because the format pin itself has been deleted at this point.
|
||||||
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
|
TArray<FString> PinNames;
|
||||||
}
|
FText::GetFormatPatternParameters(FText::FromString(FormatPattern), PinNames);
|
||||||
|
|
||||||
if (FindPin(UEdGraphSchema_K2::PN_Then) == nullptr) {
|
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
|
||||||
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);
|
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);
|
||||||
}
|
|
||||||
|
|
||||||
if (FindPin(FormatPinName, EGPD_Input) == nullptr) {
|
UEdGraphPin *FormatPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FormatPinName);
|
||||||
UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FormatPinName);
|
FormatPin->DefaultValue = FormatPattern;
|
||||||
P->DefaultValue = TEXT("Message");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a FormatMessage node, create a pin to output the result as text.
|
|
||||||
//
|
|
||||||
if (!IsFormatErrorMessage())
|
if (!IsFormatErrorMessage())
|
||||||
{
|
{
|
||||||
if (FindPin(ResultPinName, EGPD_Output) == nullptr) {
|
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Text, ResultPinName);
|
||||||
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Text, ResultPinName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is a FormatErrorMessage node, create pins that control the log verbosity
|
|
||||||
//
|
|
||||||
if (IsFormatErrorMessage())
|
if (IsFormatErrorMessage())
|
||||||
{
|
{
|
||||||
if (FindPin(VerbosityPinName, EGPD_Input) == nullptr) {
|
UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum<ElxFormatLogVerbosity>(), VerbosityPinName);
|
||||||
UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum<ElxFormatLogVerbosity>(), VerbosityPinName);
|
P->DefaultValue = TEXT("Error");
|
||||||
P->DefaultValue = TEXT("Error");
|
P->AutogeneratedDefaultValue = P->DefaultValue;
|
||||||
P->AutogeneratedDefaultValue = P->DefaultValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer all Existing Argument pins to the Old Pins Map.
|
// Create argument pins.
|
||||||
//
|
|
||||||
TMap<FString, UEdGraphPin *> OldPins;
|
|
||||||
for (auto It = Pins.CreateIterator(); It; ++It)
|
|
||||||
{
|
|
||||||
UEdGraphPin* CheckPin = *It;
|
|
||||||
if (HasPrefix(CheckPin->PinName, 'A'))
|
|
||||||
{
|
|
||||||
OldPins.Add(RemovePrefix(CheckPin->PinName).ToString(), CheckPin);
|
|
||||||
It.RemoveCurrent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Argument pins in the correct order, reusing old pins where possible.
|
|
||||||
//
|
|
||||||
for (const FString& Name : PinNames)
|
for (const FString& Name : PinNames)
|
||||||
{
|
{
|
||||||
UEdGraphPin **OldPin = OldPins.Find(Name);
|
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, AddPrefix(Name, 'A'));
|
||||||
if (OldPin != nullptr) {
|
|
||||||
Pins.Emplace(*OldPin);
|
|
||||||
OldPins.Remove(Name);
|
|
||||||
} else {
|
|
||||||
FName PrefixedName = AddPrefix(Name, 'A');
|
|
||||||
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, PrefixedName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete any unused pins.
|
|
||||||
//
|
|
||||||
for (auto &iter : OldPins)
|
|
||||||
{
|
|
||||||
iter.Value->Modify();
|
|
||||||
iter.Value->MarkAsGarbage();
|
|
||||||
}
|
|
||||||
OldPins.Empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void UK2Node_FormatMessage::SynchronizeArgumentPinType(UEdGraphPin* Pin)
|
void UK2Node_FormatMessage::SynchronizeArgumentPinType(UEdGraphPin* Pin)
|
||||||
{
|
{
|
||||||
if (HasPrefix(Pin->PinName, 'A'))
|
if (HasPrefix(Pin->PinName, 'A'))
|
||||||
{
|
{
|
||||||
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(GetSchema());
|
FEdGraphPinType DesiredType;
|
||||||
|
DesiredType.PinCategory = UEdGraphSchema_K2::PC_Wildcard;
|
||||||
bool bPinTypeChanged = false;
|
if (Pin->LinkedTo.Num() > 0)
|
||||||
if (Pin->LinkedTo.Num() == 0)
|
|
||||||
{
|
{
|
||||||
static const FEdGraphPinType WildcardPinType = FEdGraphPinType(UEdGraphSchema_K2::PC_Wildcard, NAME_None, nullptr, EPinContainerType::None, false, FEdGraphTerminalType());
|
DesiredType = Pin->LinkedTo[0]->PinType;
|
||||||
|
|
||||||
// Ensure wildcard
|
|
||||||
if (Pin->PinType != WildcardPinType)
|
|
||||||
{
|
|
||||||
Pin->PinType = WildcardPinType;
|
|
||||||
bPinTypeChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UEdGraphPin* ArgumentSourcePin = Pin->LinkedTo[0];
|
|
||||||
|
|
||||||
// Take the type of the connected pin
|
|
||||||
if (Pin->PinType != ArgumentSourcePin->PinType)
|
|
||||||
{
|
|
||||||
Pin->PinType = ArgumentSourcePin->PinType;
|
|
||||||
bPinTypeChanged = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bPinTypeChanged)
|
if (Pin->PinType != DesiredType)
|
||||||
{
|
{
|
||||||
// Let the graph know to refresh
|
Pin->PinType = DesiredType;
|
||||||
|
|
||||||
GetGraph()->NotifyNodeChanged(this);
|
GetGraph()->NotifyNodeChanged(this);
|
||||||
|
|
||||||
UBlueprint* Blueprint = GetBlueprint();
|
UBlueprint* Blueprint = GetBlueprint();
|
||||||
if (!Blueprint->bBeingCompiled)
|
if (!Blueprint->bBeingCompiled)
|
||||||
{
|
{
|
||||||
@@ -204,7 +142,7 @@ FText UK2Node_FormatMessage::GetPinDisplayName(const UEdGraphPin* Pin) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Many pins can go unlabeled if they have default values.
|
// Many pins can go unlabeled if they have default values.
|
||||||
if (IsFormatPin(Pin) || IsVerbosityPin(Pin))
|
if ((Pin->PinName == FormatPinName) || (Pin->PinName == VerbosityPinName))
|
||||||
{
|
{
|
||||||
if (Pin->LinkedTo.Num() == 0)
|
if (Pin->LinkedTo.Num() == 0)
|
||||||
{
|
{
|
||||||
@@ -212,142 +150,103 @@ FText UK2Node_FormatMessage::GetPinDisplayName(const UEdGraphPin* Pin) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For argument pins, we must strip off the Argument Pin Prefix.
|
// Just return the pin name, stripping prefix if present.
|
||||||
if (HasPrefix(Pin->PinName, 'A')) {
|
return FText::FromName(RemovePrefix(Pin->PinName));
|
||||||
return FText::FromName(RemovePrefix(Pin->PinName));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, just return the Pin Name the normal way.
|
|
||||||
return FText::FromName(Pin->PinName);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UK2Node_FormatMessage::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
|
|
||||||
{
|
|
||||||
const FName PropertyName = (PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None);
|
|
||||||
if (PropertyName == GET_MEMBER_NAME_CHECKED(UK2Node_FormatMessage, PinNames))
|
|
||||||
{
|
|
||||||
ReconstructNode();
|
|
||||||
}
|
|
||||||
Super::PostEditChangeProperty(PropertyChangedEvent);
|
|
||||||
GetGraph()->NotifyNodeChanged(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UK2Node_FormatMessage::PinConnectionListChanged(UEdGraphPin* Pin)
|
void UK2Node_FormatMessage::PinConnectionListChanged(UEdGraphPin* Pin)
|
||||||
{
|
{
|
||||||
Modify();
|
Super::PinConnectionListChanged(Pin);
|
||||||
SynchronizeArgumentPinType(Pin);
|
SynchronizeArgumentPinType(Pin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UK2Node_FormatMessage::PinDefaultValueChanged(UEdGraphPin* Pin)
|
void UK2Node_FormatMessage::PinDefaultValueChanged(UEdGraphPin* Pin)
|
||||||
{
|
{
|
||||||
if(IsFormatPin(Pin))
|
if ((Pin->PinName == FormatPinName) && (Pin->DefaultValue != FormatPattern))
|
||||||
{
|
{
|
||||||
PinNames.Empty();
|
ReconstructNode();
|
||||||
FText::GetFormatPatternParameters(FText::FromString(Pin->DefaultValue), PinNames);
|
|
||||||
CreateCorrectPins();
|
|
||||||
GetGraph()->NotifyNodeChanged(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UK2Node_FormatMessage::PinTypeChanged(UEdGraphPin* Pin)
|
|
||||||
{
|
|
||||||
SynchronizeArgumentPinType(Pin);
|
|
||||||
Super::PinTypeChanged(Pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
FText UK2Node_FormatMessage::GetTooltipText() const
|
FText UK2Node_FormatMessage::GetTooltipText() const
|
||||||
{
|
{
|
||||||
return NodeTooltip;
|
if (IsFormatErrorMessage())
|
||||||
|
{
|
||||||
|
static FText Tooltip = LOCTEXT("LogTooltip",
|
||||||
|
"Output an error, warning, or informational message to the log file.\n"
|
||||||
|
"\n"
|
||||||
|
" \u2022 Use {ArgName} to denote format arguments, giving each argument a different ArgName.\n"
|
||||||
|
"\n"
|
||||||
|
"It is often desirable to use this in conjunction with a separate utility that\n"
|
||||||
|
"pauses the execution of the blueprint whenever an error is logged.");
|
||||||
|
return Tooltip;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
static FText Tooltip = LOCTEXT("FormatTooltip",
|
||||||
|
"Format a message, and output it as Text.\n"
|
||||||
|
"\n"
|
||||||
|
" \u2022 Use {ArgName} to denote format arguments, giving each argument a different ArgName.\n"
|
||||||
|
"\n");
|
||||||
|
return Tooltip;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void UK2Node_FormatMessage::PostReconstructNode()
|
void UK2Node_FormatMessage::PostReconstructNode()
|
||||||
{
|
{
|
||||||
Super::PostReconstructNode();
|
Super::PostReconstructNode();
|
||||||
|
if (IsTemplate() || (GetGraph() == nullptr)) return;
|
||||||
UEdGraph* OuterGraph = GetGraph();
|
for (UEdGraphPin* Pin : Pins)
|
||||||
if (!IsTemplate() && OuterGraph && OuterGraph->Schema) {
|
{
|
||||||
for (UEdGraphPin* CurrentPin : Pins)
|
SynchronizeArgumentPinType(Pin);
|
||||||
{
|
|
||||||
// Potentially update an argument pin type
|
|
||||||
SynchronizeArgumentPinType(CurrentPin);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Get a function that can convert the specified type into a FFormatArgumentData.
|
UEdGraphPin* UK2Node_FormatMessage::ExpandArgumentPin(UEdGraphPin* ArgumentPin, FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
|
||||||
//
|
|
||||||
// For example:
|
|
||||||
// * if you pass in a pin of type 'String', it will return UlxFormatDataLibrary::FormatArgumentDataString
|
|
||||||
// * if you pass in a pin of type 'Vector', it will return UlxFormatDataLibrary::FormatArgumentDataVector
|
|
||||||
// and so forth.
|
|
||||||
//
|
|
||||||
UFunction *ToFormatArgumentData(const UEdGraphSchema_K2 *Schema, const FEdGraphPinType& PinType, bool AllowWild)
|
|
||||||
{
|
{
|
||||||
// Special case. Wildcard Pins are unconnected pins.
|
UFunction *Converter = UlxFormatDataLibrary::GetConverterForPinType(CompilerContext.GetSchema(), ArgumentPin->PinType, true);
|
||||||
//
|
if (Converter == nullptr)
|
||||||
if (PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard && AllowWild)
|
|
||||||
{
|
{
|
||||||
return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataBlank));
|
CompilerContext.MessageLog.Error(TEXT("Cannot convert Pin to a Format Argument"));
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scan the cached converter list for a matching type.
|
FName OriginalName = RemovePrefix(ArgumentPin->PinName);
|
||||||
//
|
UK2Node_CallFunction* ConvertNode = MakeCallFunctionNode(CompilerContext, SourceGraph, Converter);
|
||||||
UlxFormatDataLibrary* FormatDataLib = GEditor->GetEditorSubsystem<UlxFormatDataLibrary>();
|
ConvertNode->GetSchema()->TrySetDefaultValue(*ConvertNode->FindPinChecked(TEXT("Name")), OriginalName.ToString());
|
||||||
for (UFunction* Function : FormatDataLib->GetConverters())
|
|
||||||
|
UEdGraphPin *ValuePin = ConvertNode->FindPin(TEXT("AutoConvertedValue"));
|
||||||
|
if (ValuePin != nullptr)
|
||||||
{
|
{
|
||||||
FProperty* ValueProperty = Function->FindPropertyByName(TEXT("AutoConvertedValue"));
|
CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *ValuePin);
|
||||||
FEdGraphPinType ValuePinType;
|
|
||||||
bool Convertible = Schema->ConvertPropertyToPinType(ValueProperty, ValuePinType);
|
|
||||||
if (!Convertible) continue;
|
|
||||||
if (!Schema->ArePinTypesEquivalent(PinType, ValuePinType)) continue;
|
|
||||||
return Function;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A general handler for Enums. You can override this for specific enums by
|
UEdGraphPin *SubCategoryObjectPin = ConvertNode->FindPin(TEXT("PinSubCategoryObject"));
|
||||||
// putting that particular enum into any class with an AutoConvertedValue function.
|
if (SubCategoryObjectPin != nullptr)
|
||||||
//
|
|
||||||
if ((PinType.PinCategory == UEdGraphSchema_K2::PC_Byte) && (nullptr != Cast<const UEnum>(PinType.PinSubCategoryObject)))
|
|
||||||
{
|
{
|
||||||
return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataEnum));
|
SubCategoryObjectPin->DefaultObject = Cast<UObject>(ArgumentPin->PinType.PinSubCategoryObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A case for subclasses of 'Object' which are not exactly 'Object'
|
return ConvertNode->GetReturnValuePin();
|
||||||
//
|
}
|
||||||
if (PinType.PinCategory == UEdGraphSchema_K2::PC_Object)
|
|
||||||
{
|
|
||||||
return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataObject));
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't have a match.
|
|
||||||
//
|
|
||||||
return nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void UK2Node_FormatMessage::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
|
void UK2Node_FormatMessage::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
|
||||||
{
|
{
|
||||||
Super::ExpandNode(CompilerContext, SourceGraph);
|
Super::ExpandNode(CompilerContext, SourceGraph);
|
||||||
|
|
||||||
/**
|
const UEdGraphSchema_K2* CCSchema = CompilerContext.GetSchema();
|
||||||
At the end of this, the UK2Node_FormatMessage will not be a part of the Blueprint, it merely handles connecting
|
|
||||||
the other nodes into the Blueprint.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema();
|
// Parse the format pattern to extract argument names.
|
||||||
|
TArray<FString> PinNames;
|
||||||
// Create a "Make Array" node to compile the list of arguments into an array for the Format function being called
|
FText::GetFormatPatternParameters(FText::FromString(FormatPattern), PinNames);
|
||||||
UK2Node_MakeArray* MakeArrayNode = CompilerContext.SpawnIntermediateNode<UK2Node_MakeArray>(this, SourceGraph);
|
|
||||||
MakeArrayNode->AllocateDefaultPins();
|
|
||||||
CompilerContext.MessageLog.NotifyIntermediateObjectCreation(MakeArrayNode, this);
|
|
||||||
|
|
||||||
// Decide which formatting function we're going to call.
|
// Decide which formatting function we're going to call.
|
||||||
UFunction *FormatFunction;
|
UFunction *FormatFunction;
|
||||||
if (IsFormatErrorMessage())
|
if (IsFormatErrorMessage())
|
||||||
{
|
{
|
||||||
FormatFunction = UK2Node_FormatMessage::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UK2Node_FormatMessage, FormatLogMessageInternal));
|
FormatFunction = UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatLogMessageInternal));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -355,69 +254,27 @@ void UK2Node_FormatMessage::ExpandNode(class FKismetCompilerContext& CompilerCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is the node that does all the Format work and outputs the message.
|
// This is the node that does all the Format work and outputs the message.
|
||||||
UK2Node_CallFunction* CallFormatFunction = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph);
|
UK2Node_CallFunction* CallFormatFunction = MakeCallFunctionNode(CompilerContext, SourceGraph, FormatFunction);
|
||||||
CallFormatFunction->SetFromFunction(FormatFunction);
|
|
||||||
CallFormatFunction->AllocateDefaultPins();
|
|
||||||
CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallFormatFunction, this);
|
|
||||||
|
|
||||||
// Connect the output of the "Make Array" pin to the function's "InArgs" pin
|
// Create a "Make Array" node to compile the list of arguments into an array for the Format function being called.
|
||||||
|
UK2Node_MakeArray* MakeArrayNode = CompilerContext.SpawnIntermediateNode<UK2Node_MakeArray>(this, SourceGraph);
|
||||||
|
MakeArrayNode->NumInputs = PinNames.Num();
|
||||||
|
MakeArrayNode->AllocateDefaultPins();
|
||||||
|
CompilerContext.MessageLog.NotifyIntermediateObjectCreation(MakeArrayNode, this);
|
||||||
UEdGraphPin* ArrayOut = MakeArrayNode->GetOutputPin();
|
UEdGraphPin* ArrayOut = MakeArrayNode->GetOutputPin();
|
||||||
ArrayOut->MakeLinkTo(CallFormatFunction->FindPinChecked(TEXT("InArgs")));
|
ArrayOut->MakeLinkTo(CallFormatFunction->FindPinChecked(TEXT("InArgs")));
|
||||||
|
|
||||||
// This will set the "Make Array" node's type, only works if one pin is connected.
|
// This will set the "Make Array" node's type, only works if one pin is connected.
|
||||||
MakeArrayNode->PinConnectionListChanged(ArrayOut);
|
MakeArrayNode->PinConnectionListChanged(ArrayOut);
|
||||||
|
|
||||||
// For each argument, we will need to add in a "Make Struct" node.
|
// For each argument, expand it into a converter node and link to the array.
|
||||||
for(int32 ArgIdx = 0; ArgIdx < PinNames.Num(); ++ArgIdx)
|
for (int32 ArgIdx = 0; ArgIdx < PinNames.Num(); ++ArgIdx)
|
||||||
{
|
{
|
||||||
FString OriginalName = PinNames[ArgIdx];
|
UEdGraphPin* ArgumentPin = FindPin(AddPrefix(PinNames[ArgIdx], 'A'), EGPD_Input);
|
||||||
UEdGraphPin* ArgumentPin = FindPin(AddPrefix(OriginalName, 'A'), EGPD_Input);
|
UEdGraphPin* ConvertedPin = ExpandArgumentPin(ArgumentPin, CompilerContext, SourceGraph);
|
||||||
|
if (ConvertedPin == nullptr) continue;
|
||||||
|
|
||||||
// Find a function that can convert the input into an FFormatArgumentData.
|
|
||||||
UFunction *Converter = ToFormatArgumentData(Schema, ArgumentPin->PinType, true);
|
|
||||||
if (Converter == nullptr)
|
|
||||||
{
|
|
||||||
CompilerContext.MessageLog.Error(TEXT("Cannot convert Pin to a Format Argument"));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a node to call the converter.
|
|
||||||
UK2Node_CallFunction* ConvertNode = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph);
|
|
||||||
ConvertNode->SetFromFunction(Converter);
|
|
||||||
ConvertNode->AllocateDefaultPins();
|
|
||||||
CompilerContext.MessageLog.NotifyIntermediateObjectCreation(ConvertNode, this);
|
|
||||||
UEdGraphPin *ValuePin = ConvertNode->FindPin(TEXT("AutoConvertedValue"));
|
|
||||||
UEdGraphPin *NamePin = ConvertNode->FindPinChecked(TEXT("Name"));
|
|
||||||
UEdGraphPin *SubCategoryObjectPin = ConvertNode->FindPin(TEXT("PinSubCategoryObject"));
|
|
||||||
|
|
||||||
// Set a value for the 'Name' pin of the converter.
|
|
||||||
ConvertNode->GetSchema()->TrySetDefaultValue(*NamePin, OriginalName);
|
|
||||||
|
|
||||||
// Connect the Value pin of the converter, if any.
|
|
||||||
if (ValuePin != nullptr)
|
|
||||||
{
|
|
||||||
CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *ValuePin);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the converter wants to know the PinSubCategoryObject, pass it in.
|
|
||||||
if (SubCategoryObjectPin != nullptr)
|
|
||||||
{
|
|
||||||
SubCategoryObjectPin->DefaultObject = Cast<UObject>(ArgumentPin->PinType.PinSubCategoryObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The "Make Array" node already has one pin available, so don't create one for ArgIdx == 0
|
|
||||||
if(ArgIdx > 0)
|
|
||||||
{
|
|
||||||
MakeArrayNode->AddInputPin();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the input pin on the "Make Array" node by index.
|
|
||||||
const FString PinName = FString::Printf(TEXT("[%d]"), ArgIdx);
|
const FString PinName = FString::Printf(TEXT("[%d]"), ArgIdx);
|
||||||
UEdGraphPin* InputPin = MakeArrayNode->FindPinChecked(PinName);
|
ConvertedPin->MakeLinkTo(MakeArrayNode->FindPinChecked(PinName));
|
||||||
|
|
||||||
// Find the output for the pin's "Make Struct" node and link it to the corresponding pin on the "Make Array" node.
|
|
||||||
ConvertNode->GetReturnValuePin()->MakeLinkTo(InputPin);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect up other pins to the Formatting node.
|
// Connect up other pins to the Formatting node.
|
||||||
@@ -441,65 +298,30 @@ void UK2Node_FormatMessage::ExpandNode(class FKismetCompilerContext& CompilerCon
|
|||||||
|
|
||||||
UK2Node::ERedirectType UK2Node_FormatMessage::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const
|
UK2Node::ERedirectType UK2Node_FormatMessage::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const
|
||||||
{
|
{
|
||||||
ERedirectType RedirectType = ERedirectType_None;
|
if (IsTemplate() || (GetGraph() == nullptr)) return ERedirectType_None;
|
||||||
|
if ((NewPin->PinName == OldPin->PinName) &&
|
||||||
// if the pin names do match
|
(NewPin->Direction == OldPin->Direction) &&
|
||||||
if (NewPin->PinName.ToString().Equals(OldPin->PinName.ToString(), ESearchCase::CaseSensitive))
|
((NewPin->PinType == OldPin->PinType) || (NewPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard)))
|
||||||
{
|
{
|
||||||
// Make sure we're not dealing with a menu node
|
return ERedirectType_Name;
|
||||||
UEdGraph* OuterGraph = GetGraph();
|
|
||||||
if( OuterGraph && OuterGraph->Schema )
|
|
||||||
{
|
|
||||||
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(GetSchema());
|
|
||||||
if( !K2Schema || K2Schema->IsSelfPin(*NewPin) || K2Schema->ArePinTypesCompatible(OldPin->PinType, NewPin->PinType) )
|
|
||||||
{
|
|
||||||
RedirectType = ERedirectType_Name;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RedirectType = ERedirectType_None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
return ERedirectType_None;
|
||||||
{
|
|
||||||
// try looking for a redirect if it's a K2 node
|
|
||||||
if (UK2Node* Node = Cast<UK2Node>(NewPin->GetOwningNode()))
|
|
||||||
{
|
|
||||||
// if you don't have matching pin, now check if there is any redirect param set
|
|
||||||
TArray<FString> OldPinNames;
|
|
||||||
GetRedirectPinNames(*OldPin, OldPinNames);
|
|
||||||
|
|
||||||
FName NewPinName;
|
|
||||||
RedirectType = ShouldRedirectParam(OldPinNames, /*out*/ NewPinName, Node);
|
|
||||||
|
|
||||||
// make sure they match
|
|
||||||
if ((RedirectType != ERedirectType_None) && (!NewPin->PinName.ToString().Equals(NewPinName.ToString(), ESearchCase::CaseSensitive)))
|
|
||||||
{
|
|
||||||
RedirectType = ERedirectType_None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return RedirectType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UK2Node_FormatMessage::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const
|
bool UK2Node_FormatMessage::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const
|
||||||
{
|
{
|
||||||
// The following pins cannot be connected. They are meant to be hardwired constants.
|
// The following pins cannot be connected. They are meant to be hardwired constants.
|
||||||
if (IsFormatPin(MyPin))
|
if (MyPin->PinName == FormatPinName)
|
||||||
{
|
{
|
||||||
OutReason = LOCTEXT("Error_FormatStringMustBeHardwired", "Format string must be a hardwired constant.").ToString();
|
OutReason = LOCTEXT("Error_FormatStringMustBeHardwired", "Format string must be a hardwired constant.").ToString();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Argument input pins may only be connected to Byte, Integer, Float, Text, and ETextGender pins...
|
// Argument input pins may only be connected to convertible types.
|
||||||
if (HasPrefix(MyPin->PinName, 'A'))
|
if (HasPrefix(MyPin->PinName, 'A'))
|
||||||
{
|
{
|
||||||
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(GetSchema());
|
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(GetSchema());
|
||||||
const FName& OtherPinCategory = OtherPin->PinType.PinCategory;
|
UFunction *Converter = UlxFormatDataLibrary::GetConverterForPinType(K2Schema, OtherPin->PinType, false);
|
||||||
|
|
||||||
UFunction *Converter = ToFormatArgumentData(K2Schema, OtherPin->PinType, false);
|
|
||||||
if (Converter == nullptr)
|
if (Converter == nullptr)
|
||||||
{
|
{
|
||||||
OutReason = LOCTEXT("Error_InvalidArgumentType", "Data cannot be converted to text.").ToString();
|
OutReason = LOCTEXT("Error_InvalidArgumentType", "Data cannot be converted to text.").ToString();
|
||||||
@@ -513,15 +335,7 @@ bool UK2Node_FormatMessage::IsConnectionDisallowed(const UEdGraphPin* MyPin, con
|
|||||||
|
|
||||||
void UK2Node_FormatMessage::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
|
void UK2Node_FormatMessage::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
|
||||||
{
|
{
|
||||||
// actions get registered under specific object-keys; the idea is that
|
|
||||||
// actions might have to be updated (or deleted) if their object-key is
|
|
||||||
// mutated (or removed)... here we use the node's class (so if the node
|
|
||||||
// type disappears, then the action should go with it)
|
|
||||||
UClass* ActionKey = GetClass();
|
UClass* ActionKey = GetClass();
|
||||||
// to keep from needlessly instantiating a UBlueprintNodeSpawner, first
|
|
||||||
// check to make sure that the registrar is looking for actions of this type
|
|
||||||
// (could be regenerating actions for a specific asset, and therefore the
|
|
||||||
// registrar would only accept actions corresponding to that asset)
|
|
||||||
if (ActionRegistrar.IsOpenForRegistration(ActionKey))
|
if (ActionRegistrar.IsOpenForRegistration(ActionKey))
|
||||||
{
|
{
|
||||||
UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
|
UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
|
||||||
@@ -536,94 +350,5 @@ FText UK2Node_FormatMessage::GetMenuCategory() const
|
|||||||
return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Text);
|
return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
UK2Node_FormatMessage::UK2Node_FormatMessage(const FObjectInitializer& ObjectInitializer)
|
|
||||||
: Super(ObjectInitializer)
|
|
||||||
{
|
|
||||||
NodeTooltip = LOCTEXT("NodeTooltip",
|
|
||||||
"Format a message, and output it as Text.\n"
|
|
||||||
"\n"
|
|
||||||
" \u2022 Use {ArgName} to denote format arguments, giving each argument a different ArgName.\n"
|
|
||||||
"\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
UK2Node_FormatLogMessage::UK2Node_FormatLogMessage(const FObjectInitializer& ObjectInitializer)
|
|
||||||
: Super(ObjectInitializer)
|
|
||||||
{
|
|
||||||
NodeTooltip = LOCTEXT("NodeTooltip",
|
|
||||||
"Output an error, warning, or informational message to the log file.\n"
|
|
||||||
"\n"
|
|
||||||
" \u2022 Use {ArgName} to denote format arguments, giving each argument a different ArgName.\n"
|
|
||||||
"\n"
|
|
||||||
"It is often desirable to use this in conjunction with a separate utility that\n"
|
|
||||||
"pauses the execution of the blueprint whenever an error is logged."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ELogVerbosity::Type UK2Node_FormatMessage::ConvertElxFormatLogVerbosity(ElxFormatLogVerbosity Verbosity) {
|
|
||||||
switch (Verbosity) {
|
|
||||||
case ElxFormatLogVerbosity::Error: return ELogVerbosity::Error;
|
|
||||||
case ElxFormatLogVerbosity::Warning: return ELogVerbosity::Warning;
|
|
||||||
case ElxFormatLogVerbosity::Display: return ELogVerbosity::Display;
|
|
||||||
case ElxFormatLogVerbosity::Log: return ELogVerbosity::Log;
|
|
||||||
case ElxFormatLogVerbosity::ThrottledDisplay: return ELogVerbosity::Display;
|
|
||||||
case ElxFormatLogVerbosity::ThrottledLog: return ELogVerbosity::Log;
|
|
||||||
case ElxFormatLogVerbosity::Verbose: return ELogVerbosity::Verbose;
|
|
||||||
case ElxFormatLogVerbosity::VeryVerbose: return ELogVerbosity::VeryVerbose;
|
|
||||||
case ElxFormatLogVerbosity::Fatal: return ELogVerbosity::Fatal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void UK2Node_FormatMessage::FormatLogMessageInternal(UObject *Context, ElxFormatLogVerbosity Verbosity, const FString &InPattern, TArray<FFormatArgumentData> InArgs)
|
|
||||||
{
|
|
||||||
// For throttled verbosity levels, suppress repeated messages with the
|
|
||||||
// same format pattern. We key on the blueprint name + format pattern,
|
|
||||||
// and allow at most one message per second per key.
|
|
||||||
//
|
|
||||||
if (Verbosity == ElxFormatLogVerbosity::ThrottledDisplay || Verbosity == ElxFormatLogVerbosity::ThrottledLog)
|
|
||||||
{
|
|
||||||
static TMap<FString, double> LastLogTime;
|
|
||||||
double Now = FPlatformTime::Seconds();
|
|
||||||
FString Key = Context->GetClass()->GetName() + TEXT("::") + InPattern;
|
|
||||||
double &Last = LastLogTime.FindOrAdd(Key, 0.0);
|
|
||||||
if (Now - Last < 1.0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Last = Now;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the formatted string.
|
|
||||||
//
|
|
||||||
FText InPatternText(FText::FromString(InPattern));
|
|
||||||
FText Message = FTextFormatter::Format(MoveTemp(InPatternText), MoveTemp(InArgs), false, false);
|
|
||||||
FString MessageString = Message.ToString();
|
|
||||||
|
|
||||||
// Get the blueprint name.
|
|
||||||
//
|
|
||||||
// Normally, the log function expects you to pass in a filename, and a log
|
|
||||||
// category name. We use the blueprint name for both.
|
|
||||||
//
|
|
||||||
// Using the blueprint name as a log category name is not technically
|
|
||||||
// correct. However, there is no correct way to create log categories
|
|
||||||
// from inside of blueprints. Doing it this way at least produces a reasonable
|
|
||||||
// message inside the log. What doesn't work correctly is the log message
|
|
||||||
// suppression system. Ie, console commands like 'log <category> verbose'
|
|
||||||
// don't have any effect here. The design of the log message suppression
|
|
||||||
// system is such that there just is no reasonable way to hook into it from
|
|
||||||
// inside of blueprints.
|
|
||||||
//
|
|
||||||
FString BlueprintNameString = Context->GetClass()->GetName();
|
|
||||||
auto BlueprintNameAnsi = StringCast<ANSICHAR>(*BlueprintNameString);
|
|
||||||
FLogCategoryName BlueprintNameLogCategory(Context->GetClass()->GetFName());
|
|
||||||
|
|
||||||
// Output to Log
|
|
||||||
//
|
|
||||||
ELogVerbosity::Type VerbosityValue = ConvertElxFormatLogVerbosity(Verbosity);
|
|
||||||
if (VerbosityValue <= ELogVerbosity::COMPILED_IN_MINIMUM_VERBOSITY)
|
|
||||||
{
|
|
||||||
FMsg::Logf(BlueprintNameAnsi.Get(), 0, BlueprintNameLogCategory, VerbosityValue, TEXT("%s"), *MessageString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef LOCTEXT_NAMESPACE
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|||||||
@@ -19,52 +19,6 @@
|
|||||||
|
|
||||||
#include "FormatMessage.generated.h"
|
#include "FormatMessage.generated.h"
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// ElxFormatLogVerbosity
|
|
||||||
//
|
|
||||||
// Controls the ELogVerbosity of the UE_LOG directive inside
|
|
||||||
// FormatLogMessage. Also controls the throttling of log
|
|
||||||
// messages.
|
|
||||||
//
|
|
||||||
// Fatal is deliberately placed at the end so that the
|
|
||||||
// editor defaults to Error (value 0) when the dropdown is
|
|
||||||
// uninitialized. The numeric values don't match
|
|
||||||
// ELogVerbosity, so a conversion function is needed.
|
|
||||||
//
|
|
||||||
////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
UENUM(BlueprintType)
|
|
||||||
enum class ElxFormatLogVerbosity : uint8 {
|
|
||||||
|
|
||||||
/** Prints an error to the console and log file. The editor collects and reports errors. */
|
|
||||||
Error,
|
|
||||||
|
|
||||||
/** Prints a warning to the console and log file. The editor collects and reports warnings. */
|
|
||||||
Warning,
|
|
||||||
|
|
||||||
/** Prints a message to the console and log file. */
|
|
||||||
Display,
|
|
||||||
|
|
||||||
/** Prints a message to the log file, but not to the console. */
|
|
||||||
Log,
|
|
||||||
|
|
||||||
/** Like Display, but suppresses repeated messages with the same format pattern (at most once per second). */
|
|
||||||
ThrottledDisplay,
|
|
||||||
|
|
||||||
/** Like Log, but suppresses repeated messages with the same format pattern (at most once per second). */
|
|
||||||
ThrottledLog,
|
|
||||||
|
|
||||||
/** Prints a message to the log file only if Verbose logging is enabled for the given category. */
|
|
||||||
Verbose,
|
|
||||||
|
|
||||||
/** Prints a message to the log file only if VeryVerbose logging is enabled. */
|
|
||||||
VeryVerbose,
|
|
||||||
|
|
||||||
/** Prints a fatal error to the console and log file, then crashes (this crashes the editor too). */
|
|
||||||
Fatal,
|
|
||||||
};
|
|
||||||
|
|
||||||
class FBlueprintActionDatabaseRegistrar;
|
class FBlueprintActionDatabaseRegistrar;
|
||||||
class FString;
|
class FString;
|
||||||
class UEdGraph;
|
class UEdGraph;
|
||||||
@@ -79,25 +33,22 @@ class UObject;
|
|||||||
UCLASS(MinimalAPI)
|
UCLASS(MinimalAPI)
|
||||||
class UK2Node_FormatMessage : public UlxK2Node
|
class UK2Node_FormatMessage : public UlxK2Node
|
||||||
{
|
{
|
||||||
GENERATED_UCLASS_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
//~ Begin UObject Interface
|
|
||||||
virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
|
|
||||||
//~ End UObject Interface
|
|
||||||
|
|
||||||
|
public:
|
||||||
//~ Begin UEdGraphNode Interface.
|
//~ Begin UEdGraphNode Interface.
|
||||||
virtual void AllocateDefaultPins() override;
|
virtual void AllocateDefaultPins() override;
|
||||||
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
|
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
|
||||||
virtual bool ShouldShowNodeProperties() const override { return true; }
|
virtual bool ShouldShowNodeProperties() const override { return true; }
|
||||||
virtual void PinConnectionListChanged(UEdGraphPin* Pin) override;
|
virtual void PinConnectionListChanged(UEdGraphPin* Pin) override;
|
||||||
virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override;
|
virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override;
|
||||||
virtual void PinTypeChanged(UEdGraphPin* Pin) override;
|
|
||||||
virtual FText GetTooltipText() const override;
|
virtual FText GetTooltipText() const override;
|
||||||
virtual FText GetPinDisplayName(const UEdGraphPin* Pin) const override;
|
virtual FText GetPinDisplayName(const UEdGraphPin* Pin) const override;
|
||||||
//~ End UEdGraphNode Interface.
|
//~ End UEdGraphNode Interface.
|
||||||
|
|
||||||
//~ Begin UK2Node Interface.
|
//~ Begin UK2Node Interface.
|
||||||
virtual bool IsNodePure() const override { return false; }
|
virtual bool IsNodePure() const override { return false; }
|
||||||
|
virtual void ReconstructNode() override;
|
||||||
virtual void PostReconstructNode() override;
|
virtual void PostReconstructNode() override;
|
||||||
virtual bool NodeCausesStructuralBlueprintChange() const override { return true; }
|
virtual bool NodeCausesStructuralBlueprintChange() const override { return true; }
|
||||||
virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override;
|
virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override;
|
||||||
@@ -109,41 +60,38 @@ class UK2Node_FormatMessage : public UlxK2Node
|
|||||||
//~ End UK2Node Interface.
|
//~ End UK2Node Interface.
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Create all necessary pins.
|
|
||||||
//
|
|
||||||
void CreateCorrectPins();
|
|
||||||
|
|
||||||
// Synchronize the type of the given argument pin
|
// Synchronize the type of the given argument pin
|
||||||
// with the type its connected to, or reset it to
|
// with the type its connected to, or reset it to
|
||||||
// a wildcard pin if there's no connection.
|
// a wildcard pin if there's no connection.
|
||||||
//
|
//
|
||||||
void SynchronizeArgumentPinType(UEdGraphPin* Pin);
|
void SynchronizeArgumentPinType(UEdGraphPin* Pin);
|
||||||
|
|
||||||
|
// During ExpandNode, expand a single argument pin into
|
||||||
|
// a converter node. Returns the converter's output pin.
|
||||||
|
//
|
||||||
|
UEdGraphPin* ExpandArgumentPin(UEdGraphPin* ArgumentPin, class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph);
|
||||||
|
|
||||||
// Derived class sets this to true, altering
|
// Derived class sets this to true, altering
|
||||||
// the behavior of this K2Node.
|
// the behavior of this K2Node.
|
||||||
//
|
//
|
||||||
virtual bool IsFormatErrorMessage() const { return false; }
|
virtual bool IsFormatErrorMessage() const { return false; }
|
||||||
|
|
||||||
// When IsFormatErrorMessage is true, the K2Node
|
|
||||||
// macroexpands to call this function, which
|
|
||||||
// formats the message and outputs it to the log.
|
|
||||||
//
|
|
||||||
UFUNCTION(BlueprintCallable, meta=(WorldContext = "Context", BlueprintInternalUseOnly = "true"))
|
|
||||||
static void FormatLogMessageInternal(UObject *Context, ElxFormatLogVerbosity Verbosity, const FString &InPattern, TArray<FFormatArgumentData> InArgs);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static ELogVerbosity::Type ConvertElxFormatLogVerbosity(ElxFormatLogVerbosity Verbosity);
|
static const FName VerbosityPinName;
|
||||||
|
static const FName FormatPinName;
|
||||||
|
static const FName ResultPinName;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Argument names added to the node, generated as pins
|
// Whenever the format pin value changes, we call
|
||||||
// during construction.
|
// ReconstructNode, which backs up the value into this
|
||||||
|
// property. This cache is needed because during
|
||||||
|
// ReconstructNode, we blow away the format pin. The
|
||||||
|
// format pin is also absent when the node is first
|
||||||
|
// created.
|
||||||
//
|
//
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
TArray<FString> PinNames;
|
FString FormatPattern = TEXT("Message");
|
||||||
|
|
||||||
// Tooltip text for this node.
|
|
||||||
//
|
|
||||||
FText NodeTooltip;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
@@ -159,7 +107,7 @@ protected:
|
|||||||
UCLASS(MinimalAPI)
|
UCLASS(MinimalAPI)
|
||||||
class UK2Node_FormatLogMessage : public UK2Node_FormatMessage
|
class UK2Node_FormatLogMessage : public UK2Node_FormatMessage
|
||||||
{
|
{
|
||||||
GENERATED_UCLASS_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
virtual bool IsFormatErrorMessage() const override { return true; }
|
virtual bool IsFormatErrorMessage() const override { return true; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -51,17 +51,27 @@ FText UK2Node_LuaInvoke::GetTooltipText() const
|
|||||||
return Tooltip;
|
return Tooltip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UK2Node_LuaInvoke::ReconstructNode()
|
||||||
|
{
|
||||||
|
// Save the value of the Prototype Pin before it gets reconstructed.
|
||||||
|
UEdGraphPin* FunctionPin = FindPin(FunctionPinName);
|
||||||
|
if (FunctionPin != nullptr)
|
||||||
|
{
|
||||||
|
LuaFunctionPrototype = FunctionPin->DefaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Super::ReconstructNode();
|
||||||
|
}
|
||||||
|
|
||||||
void UK2Node_LuaInvoke::AllocateDefaultPins()
|
void UK2Node_LuaInvoke::AllocateDefaultPins()
|
||||||
{
|
{
|
||||||
Pins.Reset();
|
Pins.Reset();
|
||||||
Super::AllocateDefaultPins();
|
Super::AllocateDefaultPins();
|
||||||
|
|
||||||
if (LuaFunctionPrototype.IsEmpty())
|
|
||||||
{
|
|
||||||
LuaFunctionPrototype = TEXT("class.func(int arg1, int arg2) : int ret1, int ret2");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the lua function prototype.
|
// Parse the lua function prototype.
|
||||||
|
// Note that we use the saved value of the function
|
||||||
|
// prototype, because the function prototype pin
|
||||||
|
// has been deleted at this point.
|
||||||
FlxParsedProto ParsedProto(LuaFunctionPrototype);
|
FlxParsedProto ParsedProto(LuaFunctionPrototype);
|
||||||
if (!ParsedProto.ErrorMessage.IsEmpty())
|
if (!ParsedProto.ErrorMessage.IsEmpty())
|
||||||
{
|
{
|
||||||
@@ -142,7 +152,6 @@ void UK2Node_LuaInvoke::PinDefaultValueChanged(UEdGraphPin* Pin)
|
|||||||
{
|
{
|
||||||
if ((Pin->PinName == FunctionPinName) && (Pin->DefaultValue != LuaFunctionPrototype))
|
if ((Pin->PinName == FunctionPinName) && (Pin->DefaultValue != LuaFunctionPrototype))
|
||||||
{
|
{
|
||||||
LuaFunctionPrototype = Pin->DefaultValue;
|
|
||||||
ReconstructNode();
|
ReconstructNode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,6 +255,7 @@ void UK2Node_LuaInvoke::ExpandNode(class FKismetCompilerContext& CompilerContext
|
|||||||
|
|
||||||
UK2Node::ERedirectType UK2Node_LuaInvoke::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const
|
UK2Node::ERedirectType UK2Node_LuaInvoke::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const
|
||||||
{
|
{
|
||||||
|
if (IsTemplate() || (GetGraph() == nullptr)) return ERedirectType_None;
|
||||||
if ((NewPin->PinName == OldPin->PinName) &&
|
if ((NewPin->PinName == OldPin->PinName) &&
|
||||||
(NewPin->Direction == OldPin->Direction) &&
|
(NewPin->Direction == OldPin->Direction) &&
|
||||||
(NewPin->PinType == OldPin->PinType))
|
(NewPin->PinType == OldPin->PinType))
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public:
|
|||||||
|
|
||||||
//~ Begin UK2Node Interface.
|
//~ Begin UK2Node Interface.
|
||||||
virtual bool IsNodePure() const override { return false; }
|
virtual bool IsNodePure() const override { return false; }
|
||||||
|
virtual void ReconstructNode() override;
|
||||||
virtual bool NodeCausesStructuralBlueprintChange() const override { return true; }
|
virtual bool NodeCausesStructuralBlueprintChange() const override { return true; }
|
||||||
virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override;
|
virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override;
|
||||||
virtual ERedirectType DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const override;
|
virtual ERedirectType DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const override;
|
||||||
@@ -51,9 +52,15 @@ private:
|
|||||||
static const FName ErrorPinName;
|
static const FName ErrorPinName;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/** The lua function prototype, which must be saved as a property **/
|
// Whenever the function value changes, we call
|
||||||
|
// ReconstructNode, which backs up the value into this
|
||||||
|
// property. This cache is needed because during
|
||||||
|
// ReconstructNode, we blow away the function pin. The
|
||||||
|
// function pin is also absent when the node is first
|
||||||
|
// created.
|
||||||
|
//
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString LuaFunctionPrototype;
|
FString LuaFunctionPrototype = TEXT("class.func(int arg1, int arg2) : int ret1, int ret2");
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user