Finished implementing the ReadLuaValues K2Node

This commit is contained in:
2026-03-04 19:54:13 -05:00
parent 78f0f318c5
commit 6d018fc02b
6 changed files with 199 additions and 250 deletions

View File

@@ -1,4 +1,3 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ReadLuaValues.h"
@@ -6,58 +5,42 @@
#include "BlueprintActionDatabaseRegistrar.h"
#include "BlueprintNodeSpawner.h"
#include "EdGraphSchema_K2.h"
#include "GameFramework/Actor.h"
#include "K2Node_CallFunction.h"
#include "KismetCompiler.h"
#include "LuaCall.h"
#define LOCTEXT_NAMESPACE "LuaCall"
#define LOCTEXT_NAMESPACE "ReadLuaValues"
// All argument pins will have internal Names that start with "A:"
const FName UK2Node_ReadLuaValues::FunctionPinName(TEXT("Lua Function Prototype"));
const FName UK2Node_ReadLuaValues::PlacePinName(TEXT("Place Tangible"));
const FName UK2Node_ReadLuaValues::ExtraResultsPinName(TEXT("Extra Results"));
const FName UK2Node_ReadLuaValues::PrototypePinName(TEXT("Prototype"));
const FName UK2Node_ReadLuaValues::InputValuesPinName(TEXT("Input Values"));
const FName UK2Node_ReadLuaValues::RemainingPinName(TEXT("Remaining"));
const FName UK2Node_ReadLuaValues::ErrorPinName(TEXT("Error"));
FText UK2Node_ReadLuaValues::GetTooltipText() const
{
static FText Tooltip = FText::FromString(FString::Printf(TEXT(
"Call a Lua function.\n"
"Read typed values from a Lua Values array.\n"
"\n"
"The lua function prototype must be a hardwired string which must look like\n"
"one of the following:\n"
"The value prototype must be a hardwired string listing the\n"
"types and names of the values to read, for example:\n"
"\n"
" classname.funcname(int arg1, int arg2)\n"
" classname.funcname(int arg1, int arg2) : int ret1, int ret2\n"
" classname.funcname(int arg1, int arg2) : int ret1, int ret2, ...\n"
" string x, float y, int z\n"
"\n"
"You must specify types for the argument and return value pins. The\n"
"types that you may specify are:\n"
"If you add '...' at the end, any remaining values will\n"
"be available through the Remaining output pin.\n"
"\n"
"Arguments: %s\n"
"Return Values: %s\n"
"\n"
"The prototype is parsed to determine what lua function to call.\n"
"The lua call node will automatically add pins for the arguments and\n"
"return values. If the function has no return values, you can omit those\n"
"from the proto. If you put an ellipsis at the end of the return values,\n"
"then any additional return values will be collected into a\n"
"dynamically typed array of values which you can iterate over later.\n"
"\n"
"Optionally, you may use the * wildcard for the classname. In that\n"
"case, the class of the 'place' tangible will be used.\n"
"\n"), *UlxLuaCallLibrary::AllKnownArgumentTypes(), *UlxLuaCallLibrary::AllKnownReturnValueTypes()));
"Supported types: %s\n"),
*UlxLuaCallLibrary::AllKnownReturnValueTypes()));
return Tooltip;
}
void UK2Node_ReadLuaValues::ReconstructNode()
{
// Save the value of the Prototype Pin before it gets reconstructed.
UEdGraphPin* FunctionPin = FindPin(FunctionPinName);
if (FunctionPin != nullptr)
UEdGraphPin* PrototypePin = FindPin(PrototypePinName);
if (PrototypePin != nullptr)
{
LuaFunctionPrototype = FunctionPin->DefaultValue;
ValuePrototype = PrototypePin->DefaultValue;
}
Super::ReconstructNode();
@@ -68,48 +51,29 @@ void UK2Node_ReadLuaValues::AllocateDefaultPins()
Pins.Reset();
Super::AllocateDefaultPins();
// 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 = FlxParsedProto::ParsePrototype(LuaFunctionPrototype);
// Parse the value prototype string.
FlxParsedProto ParsedProto = FlxParsedProto::ParseReturnValuesOnly(ValuePrototype);
if (!ParsedProto.ErrorMessage.IsEmpty())
{
SetErrorMsg(FString::Printf(TEXT("Syntax error in lua function prototype: %s"), *ParsedProto.ErrorMessage));
SetErrorMsg(FString::Printf(TEXT("Syntax error in value prototype: %s"), *ParsedProto.ErrorMessage));
}
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, ErrorPinName);
if (!IsInvoke())
{
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, ErrorPinName);
}
UEdGraphPin *PrototypePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, PrototypePinName);
PrototypePin->DefaultValue = ValuePrototype;
UEdGraphPin *FunctionPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FunctionPinName);
FunctionPin->DefaultValue = LuaFunctionPrototype;
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UlxLuaValues::StaticClass(), InputValuesPinName);
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, AActor::StaticClass(), PlacePinName);
// Create argument pins.
for (const FlxParsedProto::Pin & Pin : ParsedProto.Arguments)
{
FName PrefixedName = AddPrefix(Pin.Name, 'A');
UFunction *Accessor = UlxLuaCallLibrary::GetArgumentPacker(Pin.Type);
if (Accessor == nullptr) {
SetErrorMsg(FString::Printf(TEXT("Unknown argument type: %s"), *Pin.Type));
continue;
}
CreatePin(EGPD_Input, PropertyToPinType(Accessor->FindPropertyByName(TEXT("Value"))), PrefixedName);
}
// Create return value pins.
// Create output pins for each value.
for (const FlxParsedProto::Pin & Pin : ParsedProto.ReturnValues)
{
FName PrefixedName = AddPrefix(Pin.Name, 'R');
UFunction *Accessor = UlxLuaCallLibrary::GetReturnValueUnpacker(Pin.Type);
if (Accessor == nullptr) {
SetErrorMsg(FString::Printf(TEXT("Unknown return value type: %s"), *Pin.Type));
SetErrorMsg(FString::Printf(TEXT("Unknown value type: %s"), *Pin.Type));
continue;
}
CreatePin(EGPD_Output, PropertyToPinType(Accessor->FindPropertyByName(TEXT("Result"))), PrefixedName);
@@ -117,21 +81,14 @@ void UK2Node_ReadLuaValues::AllocateDefaultPins()
if (ParsedProto.ExtraReturnValues)
{
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Object, UlxLuaValues::StaticClass(), ExtraResultsPinName);
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Object, UlxLuaValues::StaticClass(), RemainingPinName);
}
}
FText UK2Node_ReadLuaValues::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
if (IsInvoke())
{
return LOCTEXT("LuaInvoke_Title", "Invoke a Lua Function");
}
else
{
return LOCTEXT("LuaProbe_Title", "Probe a Lua Function");
}
return LOCTEXT("ReadLuaValues_Title", "Read Lua Values");
}
FText UK2Node_ReadLuaValues::GetPinDisplayName(const UEdGraphPin* Pin) const
@@ -139,115 +96,72 @@ FText UK2Node_ReadLuaValues::GetPinDisplayName(const UEdGraphPin* Pin) const
// These pins don't need labels.
if ((Pin->PinName == UEdGraphSchema_K2::PN_Execute) ||
(Pin->PinName == UEdGraphSchema_K2::PN_Then) ||
(Pin->PinName == FunctionPinName))
(Pin->PinName == PrototypePinName))
{
return FText::GetEmpty();
}
// Return the pin name, removing A: or R: if present.
// Return the pin name, removing R: prefix if present.
return FText::FromName(RemovePrefix(Pin->PinName));
}
void UK2Node_ReadLuaValues::PinDefaultValueChanged(UEdGraphPin* Pin)
{
if ((Pin->PinName == FunctionPinName) && (Pin->DefaultValue != LuaFunctionPrototype))
if ((Pin->PinName == PrototypePinName) && (Pin->DefaultValue != ValuePrototype))
{
ReconstructNode();
}
}
#define LuaCallLibraryFunction(name) (UlxLuaCallLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxLuaCallLibrary, name)))
void UK2Node_ReadLuaValues::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
Super::ExpandNode(CompilerContext, SourceGraph);
// The BeginNode function packs the class name and function name into the call buffer.
FlxParsedProto ParsedProto = FlxParsedProto::ParsePrototype(LuaFunctionPrototype);
const UEdGraphSchema_K2* CCSchema = CompilerContext.GetSchema();
UK2Node_CallFunction* BeginNode = MakeCallFunctionNode(CompilerContext, SourceGraph, LuaCallLibraryFunction(LuaCallBegin));
CCSchema->TrySetDefaultValue(*BeginNode->FindPinChecked(TEXT("ClassName")), ParsedProto.ClassName);
CCSchema->TrySetDefaultValue(*BeginNode->FindPinChecked(TEXT("FunctionName")), ParsedProto.FunctionName);
UEdGraphPin *ThenPin = BeginNode->GetThenPin();
// Add Packing operations for all argument pins.
for (const FlxParsedProto::Pin &PinInfo : ParsedProto.Arguments)
FlxParsedProto ParsedProto = FlxParsedProto::ParseReturnValuesOnly(ValuePrototype);
UEdGraphPin *InputInputValuesCopyPin = FindPinChecked(InputValuesPinName);
// Save the cursor so we can restore it on error.
// SaveCursor returns the UlxLuaValues*, which we use as the
// intermediate pin for all subsequent nodes.
UFunction *SaveCursorFunc = UlxLuaValues::StaticClass()->FindFunctionByName(TEXT("SaveCursor"));
UK2Node_CallFunction *SaveCursorNode = MakeCallFunctionNode(CompilerContext, SourceGraph, SaveCursorFunc);
CompilerContext.MovePinLinksToIntermediate(*InputInputValuesCopyPin, *SaveCursorNode->FindPinChecked(UEdGraphSchema_K2::PN_Self));
CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *SaveCursorNode->GetExecPin());
UEdGraphPin *InputValuesCopyPin = SaveCursorNode->GetReturnValuePin();
UEdGraphPin *ThenPin = SaveCursorNode->GetThenPin();
// The Read functions automatically restore the cursor on failure,
// so we just need a pin to wire WrongType outputs to.
UEdGraphPin *ErrorExecPin = FindPinChecked(ErrorPinName);
// Add Unpacking operations for all output pins.
for (const FlxParsedProto::Pin &PinInfo : ParsedProto.ReturnValues)
{
UEdGraphPin *Pin = FindPinChecked(AddPrefix(PinInfo.Name, 'A'));
UFunction *PackingFunc = UlxLuaCallLibrary::GetArgumentPacker(PinInfo.Type);
if (PackingFunc == nullptr)
UEdGraphPin *Pin = FindPinChecked(AddPrefix(PinInfo.Name, 'R'));
UFunction *UnpackingFunc = UlxLuaCallLibrary::GetReturnValueUnpacker(PinInfo.Type);
if (UnpackingFunc == nullptr)
{
// This codepath should be unreachable, but just in case.
CompilerContext.MessageLog.Error(TEXT("All argument pins must have known types"));
CompilerContext.MessageLog.Error(TEXT("All value pins must have known types."));
continue;
}
UK2Node_CallFunction *PackNode = MakeCallFunctionNode(CompilerContext, SourceGraph, PackingFunc);
CompilerContext.MovePinLinksToIntermediate(*Pin, *PackNode->FindPinChecked(TEXT("Value")));
ThenPin = ChainExecPin(ThenPin, PackNode, TEXT("Then"));
UK2Node_CallFunction *UnpackNode = MakeCallFunctionNode(CompilerContext, SourceGraph, UnpackingFunc);
InputValuesCopyPin->MakeLinkTo(UnpackNode->FindPinChecked(UEdGraphSchema_K2::PN_Self));
CompilerContext.CopyPinLinksToIntermediate(*ErrorExecPin, *UnpackNode->FindPinChecked(TEXT("WrongType")));
CompilerContext.MovePinLinksToIntermediate(*Pin, *UnpackNode->FindPinChecked(TEXT("Result")));
ThenPin = ChainExecPin(ThenPin, UnpackNode, TEXT("Success"));
}
// Add the invoke or probe node.
UEdGraphPin *ReturnArrayPin = nullptr;
if (IsInvoke())
// If there is a Remaining output pin, pass through the LuaValues object.
// The cursor is already past the consumed values.
if (ParsedProto.ExtraReturnValues)
{
UK2Node_CallFunction* ActionNode = MakeCallFunctionNode(CompilerContext, SourceGraph, LuaCallLibraryFunction(LuaCallInvoke));
CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(PlacePinName), *ActionNode->FindPinChecked(TEXT("Place")));
ThenPin = ChainExecPin(ThenPin, ActionNode, TEXT("Then"));
}
else
{
UK2Node_CallFunction* ActionNode = MakeCallFunctionNode(CompilerContext, SourceGraph, LuaCallLibraryFunction(LuaCallProbe));
CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(PlacePinName), *ActionNode->FindPinChecked(TEXT("Place")));
CompilerContext.CopyPinLinksToIntermediate(*FindPinChecked(ErrorPinName), *ActionNode->FindPinChecked(TEXT("False")));
ReturnArrayPin = ActionNode->FindPinChecked(TEXT("ReturnArray"));
ThenPin = ChainExecPin(ThenPin, ActionNode, TEXT("True"));
}
if (IsInvoke())
{
// Make sure we didn't have return values for an invoke.
if ((ParsedProto.ReturnValues.Num() > 0) || (ParsedProto.ExtraReturnValues))
{
CompilerContext.MessageLog.Error(TEXT("Lua Call in Invoke mode does not support return values"));
}
}
else
{
// Add Unpacking operations for all return value pins.
for (const FlxParsedProto::Pin &PinInfo : ParsedProto.ReturnValues)
{
UEdGraphPin *Pin = FindPinChecked(AddPrefix(PinInfo.Name, 'R'));
UFunction *UnpackingFunc = UlxLuaCallLibrary::GetReturnValueUnpacker(PinInfo.Type);
if (UnpackingFunc == nullptr)
{
// This codepath should be unreachable, but just in case.
CompilerContext.MessageLog.Error(TEXT("All return value pins must have known types."));
continue;
}
UK2Node_CallFunction *UnpackNode = MakeCallFunctionNode(CompilerContext, SourceGraph, UnpackingFunc);
ReturnArrayPin->MakeLinkTo(UnpackNode->FindPinChecked(UEdGraphSchema_K2::PN_Self));
CompilerContext.CopyPinLinksToIntermediate(*FindPinChecked(ErrorPinName), *UnpackNode->FindPinChecked(TEXT("WrongType")));
CompilerContext.MovePinLinksToIntermediate(*Pin, *UnpackNode->FindPinChecked(TEXT("Result")));
ThenPin = ChainExecPin(ThenPin, UnpackNode, TEXT("Success"));
}
// If there is an extra results pin, hook it up.
if (ParsedProto.ExtraReturnValues)
{
UEdGraphPin *ExtraPin = FindPinChecked(ExtraResultsPinName);
UFunction *DiscardFunc = UlxLuaValues::StaticClass()->FindFunctionByName(TEXT("DiscardBeforeCursor"));
UK2Node_CallFunction *DiscardNode = MakeCallFunctionNode(CompilerContext, SourceGraph, DiscardFunc);
ReturnArrayPin->MakeLinkTo(DiscardNode->FindPinChecked(UEdGraphSchema_K2::PN_Self));
ThenPin = ChainExecPin(ThenPin, DiscardNode, TEXT("Then"));
CompilerContext.MovePinLinksToIntermediate(*ExtraPin, *ReturnArrayPin);
}
UEdGraphPin *RemainingPin = FindPinChecked(RemainingPinName);
CompilerContext.MovePinLinksToIntermediate(*RemainingPin, *InputValuesCopyPin);
}
// Link up the Exec pins.
CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *BeginNode->GetExecPin());
// Link up the output exec pin.
CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *ThenPin);
BreakAllNodeLinks();
}
@@ -267,10 +181,10 @@ UK2Node::ERedirectType UK2Node_ReadLuaValues::DoPinsMatchForReconstruction(const
bool UK2Node_ReadLuaValues::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const
{
// The function pin cannot be connected.
if (MyPin->PinName == FunctionPinName)
// The prototype pin cannot be connected.
if (MyPin->PinName == PrototypePinName)
{
OutReason = LOCTEXT("Error_FunctionPrototypeMustBeHardwired", "Lua function prototype must be a hardwired constant.").ToString();
OutReason = LOCTEXT("Error_PrototypeMustBeHardwired", "Value prototype must be a hardwired constant.").ToString();
return true;
}