Massive refactoring in FormatMessage and LuaCallNode

This commit is contained in:
2026-03-04 06:47:37 -05:00
parent 6428194393
commit cb6f9ebe1d
7 changed files with 318 additions and 467 deletions

View File

@@ -2,7 +2,6 @@
#include "FormatMessage.h"
#include "Editor.h"
#include "Internationalization/TextFormatter.h"
#include "BlueprintActionDatabaseRegistrar.h"
@@ -44,136 +43,75 @@
// All argument pins will have Names that start with "A:"
static const FName VerbosityPinName(TEXT("Verbosity"));
static bool IsVerbosityPin(const UEdGraphPin *Pin) {
return (Pin->PinName == VerbosityPinName);
}
const FName UK2Node_FormatMessage::VerbosityPinName(TEXT("Verbosity"));
const FName UK2Node_FormatMessage::FormatPinName(TEXT("Format"));
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"));
static bool IsResultPin(const UEdGraphPin *Pin) {
return (Pin->PinName == ResultPinName);
}
void UK2Node_FormatMessage::ReconstructNode()
{
// 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()
{
Pins.Reset();
Super::AllocateDefaultPins();
CreateCorrectPins();
}
void UK2Node_FormatMessage::CreateCorrectPins()
{
if (FindPin(UEdGraphSchema_K2::PN_Execute) == nullptr) {
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
}
// Parse the format pattern to extract argument names.
// Note that we use the saved value of the format pattern,
// because the format pin itself has been deleted at this point.
TArray<FString> PinNames;
FText::GetFormatPatternParameters(FText::FromString(FormatPattern), PinNames);
if (FindPin(UEdGraphSchema_K2::PN_Then) == nullptr) {
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);
}
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);
if (FindPin(FormatPinName, EGPD_Input) == nullptr) {
UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FormatPinName);
P->DefaultValue = TEXT("Message");
}
UEdGraphPin *FormatPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FormatPinName);
FormatPin->DefaultValue = FormatPattern;
// If this is a FormatMessage node, create a pin to output the result as text.
//
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 (FindPin(VerbosityPinName, EGPD_Input) == nullptr) {
UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum<ElxFormatLogVerbosity>(), VerbosityPinName);
P->DefaultValue = TEXT("Error");
P->AutogeneratedDefaultValue = P->DefaultValue;
}
UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum<ElxFormatLogVerbosity>(), VerbosityPinName);
P->DefaultValue = TEXT("Error");
P->AutogeneratedDefaultValue = P->DefaultValue;
}
// Transfer all Existing Argument pins to the Old Pins Map.
//
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.
//
// Create argument pins.
for (const FString& Name : PinNames)
{
UEdGraphPin **OldPin = OldPins.Find(Name);
if (OldPin != nullptr) {
Pins.Emplace(*OldPin);
OldPins.Remove(Name);
} else {
FName PrefixedName = AddPrefix(Name, 'A');
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, PrefixedName);
}
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, AddPrefix(Name, 'A'));
}
// Delete any unused pins.
//
for (auto &iter : OldPins)
{
iter.Value->Modify();
iter.Value->MarkAsGarbage();
}
OldPins.Empty();
}
void UK2Node_FormatMessage::SynchronizeArgumentPinType(UEdGraphPin* Pin)
{
if (HasPrefix(Pin->PinName, 'A'))
{
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(GetSchema());
bool bPinTypeChanged = false;
if (Pin->LinkedTo.Num() == 0)
FEdGraphPinType DesiredType;
DesiredType.PinCategory = UEdGraphSchema_K2::PC_Wildcard;
if (Pin->LinkedTo.Num() > 0)
{
static const FEdGraphPinType WildcardPinType = FEdGraphPinType(UEdGraphSchema_K2::PC_Wildcard, NAME_None, nullptr, EPinContainerType::None, false, FEdGraphTerminalType());
// 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;
}
DesiredType = Pin->LinkedTo[0]->PinType;
}
if (bPinTypeChanged)
if (Pin->PinType != DesiredType)
{
// Let the graph know to refresh
Pin->PinType = DesiredType;
GetGraph()->NotifyNodeChanged(this);
UBlueprint* Blueprint = GetBlueprint();
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.
if (IsFormatPin(Pin) || IsVerbosityPin(Pin))
if ((Pin->PinName == FormatPinName) || (Pin->PinName == VerbosityPinName))
{
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.
if (HasPrefix(Pin->PinName, 'A')) {
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);
// Just return the pin name, stripping prefix if present.
return FText::FromName(RemovePrefix(Pin->PinName));
}
void UK2Node_FormatMessage::PinConnectionListChanged(UEdGraphPin* Pin)
{
Modify();
Super::PinConnectionListChanged(Pin);
SynchronizeArgumentPinType(Pin);
}
void UK2Node_FormatMessage::PinDefaultValueChanged(UEdGraphPin* Pin)
{
if(IsFormatPin(Pin))
if ((Pin->PinName == FormatPinName) && (Pin->DefaultValue != FormatPattern))
{
PinNames.Empty();
FText::GetFormatPatternParameters(FText::FromString(Pin->DefaultValue), PinNames);
CreateCorrectPins();
GetGraph()->NotifyNodeChanged(this);
ReconstructNode();
}
}
void UK2Node_FormatMessage::PinTypeChanged(UEdGraphPin* Pin)
{
SynchronizeArgumentPinType(Pin);
Super::PinTypeChanged(Pin);
}
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()
{
Super::PostReconstructNode();
UEdGraph* OuterGraph = GetGraph();
if (!IsTemplate() && OuterGraph && OuterGraph->Schema) {
for (UEdGraphPin* CurrentPin : Pins)
{
// Potentially update an argument pin type
SynchronizeArgumentPinType(CurrentPin);
}
if (IsTemplate() || (GetGraph() == nullptr)) return;
for (UEdGraphPin* Pin : Pins)
{
SynchronizeArgumentPinType(Pin);
}
}
// Get a function that can convert the specified type into a FFormatArgumentData.
//
// 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)
UEdGraphPin* UK2Node_FormatMessage::ExpandArgumentPin(UEdGraphPin* ArgumentPin, FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
// Special case. Wildcard Pins are unconnected pins.
//
if (PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard && AllowWild)
UFunction *Converter = UlxFormatDataLibrary::GetConverterForPinType(CompilerContext.GetSchema(), ArgumentPin->PinType, true);
if (Converter == nullptr)
{
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.
//
UlxFormatDataLibrary* FormatDataLib = GEditor->GetEditorSubsystem<UlxFormatDataLibrary>();
for (UFunction* Function : FormatDataLib->GetConverters())
FName OriginalName = RemovePrefix(ArgumentPin->PinName);
UK2Node_CallFunction* ConvertNode = MakeCallFunctionNode(CompilerContext, SourceGraph, Converter);
ConvertNode->GetSchema()->TrySetDefaultValue(*ConvertNode->FindPinChecked(TEXT("Name")), OriginalName.ToString());
UEdGraphPin *ValuePin = ConvertNode->FindPin(TEXT("AutoConvertedValue"));
if (ValuePin != nullptr)
{
FProperty* ValueProperty = Function->FindPropertyByName(TEXT("AutoConvertedValue"));
FEdGraphPinType ValuePinType;
bool Convertible = Schema->ConvertPropertyToPinType(ValueProperty, ValuePinType);
if (!Convertible) continue;
if (!Schema->ArePinTypesEquivalent(PinType, ValuePinType)) continue;
return Function;
CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *ValuePin);
}
// A general handler for Enums. You can override this for specific enums by
// putting that particular enum into any class with an AutoConvertedValue function.
//
if ((PinType.PinCategory == UEdGraphSchema_K2::PC_Byte) && (nullptr != Cast<const UEnum>(PinType.PinSubCategoryObject)))
UEdGraphPin *SubCategoryObjectPin = ConvertNode->FindPin(TEXT("PinSubCategoryObject"));
if (SubCategoryObjectPin != nullptr)
{
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'
//
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;
};
return ConvertNode->GetReturnValuePin();
}
void UK2Node_FormatMessage::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
Super::ExpandNode(CompilerContext, SourceGraph);
/**
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* CCSchema = CompilerContext.GetSchema();
const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema();
// 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->AllocateDefaultPins();
CompilerContext.MessageLog.NotifyIntermediateObjectCreation(MakeArrayNode, this);
// Parse the format pattern to extract argument names.
TArray<FString> PinNames;
FText::GetFormatPatternParameters(FText::FromString(FormatPattern), PinNames);
// Decide which formatting function we're going to call.
UFunction *FormatFunction;
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
{
@@ -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.
UK2Node_CallFunction* CallFormatFunction = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph);
CallFormatFunction->SetFromFunction(FormatFunction);
CallFormatFunction->AllocateDefaultPins();
CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallFormatFunction, this);
UK2Node_CallFunction* CallFormatFunction = MakeCallFunctionNode(CompilerContext, SourceGraph, FormatFunction);
// 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();
ArrayOut->MakeLinkTo(CallFormatFunction->FindPinChecked(TEXT("InArgs")));
// This will set the "Make Array" node's type, only works if one pin is connected.
MakeArrayNode->PinConnectionListChanged(ArrayOut);
// For each argument, we will need to add in a "Make Struct" node.
for(int32 ArgIdx = 0; ArgIdx < PinNames.Num(); ++ArgIdx)
// For each argument, expand it into a converter node and link to the array.
for (int32 ArgIdx = 0; ArgIdx < PinNames.Num(); ++ArgIdx)
{
FString OriginalName = PinNames[ArgIdx];
UEdGraphPin* ArgumentPin = FindPin(AddPrefix(OriginalName, 'A'), EGPD_Input);
// 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.
UEdGraphPin* ArgumentPin = FindPin(AddPrefix(PinNames[ArgIdx], 'A'), EGPD_Input);
UEdGraphPin* ConvertedPin = ExpandArgumentPin(ArgumentPin, CompilerContext, SourceGraph);
if (ConvertedPin == nullptr) continue;
const FString PinName = FString::Printf(TEXT("[%d]"), ArgIdx);
UEdGraphPin* InputPin = 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);
ConvertedPin->MakeLinkTo(MakeArrayNode->FindPinChecked(PinName));
}
// 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
{
ERedirectType RedirectType = ERedirectType_None;
// if the pin names do match
if (NewPin->PinName.ToString().Equals(OldPin->PinName.ToString(), ESearchCase::CaseSensitive))
if (IsTemplate() || (GetGraph() == nullptr)) return ERedirectType_None;
if ((NewPin->PinName == OldPin->PinName) &&
(NewPin->Direction == OldPin->Direction) &&
((NewPin->PinType == OldPin->PinType) || (NewPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard)))
{
// Make sure we're not dealing with a menu node
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;
}
}
return ERedirectType_Name;
}
else
{
// 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;
return ERedirectType_None;
}
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.
if (IsFormatPin(MyPin))
if (MyPin->PinName == FormatPinName)
{
OutReason = LOCTEXT("Error_FormatStringMustBeHardwired", "Format string must be a hardwired constant.").ToString();
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'))
{
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(GetSchema());
const FName& OtherPinCategory = OtherPin->PinType.PinCategory;
UFunction *Converter = ToFormatArgumentData(K2Schema, OtherPin->PinType, false);
UFunction *Converter = UlxFormatDataLibrary::GetConverterForPinType(K2Schema, OtherPin->PinType, false);
if (Converter == nullptr)
{
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
{
// 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();
// 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))
{
UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
@@ -536,94 +350,5 @@ FText UK2Node_FormatMessage::GetMenuCategory() const
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