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

Binary file not shown.

View File

@@ -276,7 +276,7 @@ bool UlxLuaCallLibrary::LuaCallProbe(UObject *context, AActor *place, UlxLuaValu
} }
ElxSuccessOrWrongType Status; ElxSuccessOrWrongType Status;
FString ErrorMessage; FString ErrorMessage;
ReturnArray->ReadString(Status, ErrorMessage, false); ReturnArray->ReadString(Status, ErrorMessage);
if (Status != ElxSuccessOrWrongType::Success) if (Status != ElxSuccessOrWrongType::Success)
{ {
UE_LOG(LogLuprexIntegration, Error, TEXT("lua probe should always return an error message (possibly empty) as the first parameter")); UE_LOG(LogLuprexIntegration, Error, TEXT("lua probe should always return an error message (possibly empty) as the first parameter"));
@@ -289,6 +289,7 @@ bool UlxLuaCallLibrary::LuaCallProbe(UObject *context, AActor *place, UlxLuaValu
ReturnArray = nullptr; ReturnArray = nullptr;
return false; return false;
} }
ReturnArray->DiscardBeforeCursor();
return true; return true;
} }
@@ -383,7 +384,6 @@ bool UlxLuaValues::Initialize(std::string_view data)
while (!Decoder.empty()) while (!Decoder.empty())
{ {
LuaValueType Tag = Decoder.read_lua_value_type(); LuaValueType Tag = Decoder.read_lua_value_type();
int64 Pos = Decoder.total_reads(); int64 Pos = Decoder.total_reads();
ElxLuaValueType Type; ElxLuaValueType Type;
switch (Tag) switch (Tag)
@@ -411,7 +411,7 @@ FString UlxLuaValues::DebugString() const
Output << TEXT("{ "); Output << TEXT("{ ");
for (int i = 0; i < Types.Num(); i++) for (int i = 0; i < Types.Num(); i++)
{ {
if (i > 0) Output << TEXT(", "); if (i > 0) Output << TEXT(" ");
if (i == Cursor) Output << TEXT("^ "); if (i == Cursor) Output << TEXT("^ ");
@@ -442,31 +442,15 @@ FString UlxLuaValues::DebugString() const
return Output.ToString(); return Output.ToString();
} }
ElxSuccessOrWrongType UlxLuaValues::CheckType(bool LogErrorOnWrongType, ElxLuaValueType Type, ElxLuaValueType Desired) ElxSuccessOrWrongType UlxLuaValues::CheckType(ElxLuaValueType Type, ElxLuaValueType Desired)
{ {
if (Type != Desired) if (Type != Desired)
{ {
if (LogErrorOnWrongType)
{
FString TypeName = StaticEnum<ElxLuaValueType>()->GetDisplayNameTextByValue(int64(Type)).ToString();
FString DesiredName = StaticEnum<ElxLuaValueType>()->GetDisplayNameTextByValue(int64(Desired)).ToString();
UE_LOG(LogBlueprint, Error, TEXT("Expected a value of type %s, but found %s instead."), *DesiredName, *TypeName);
}
return ElxSuccessOrWrongType::WrongType; return ElxSuccessOrWrongType::WrongType;
} }
return ElxSuccessOrWrongType::Success; return ElxSuccessOrWrongType::Success;
} }
void UlxLuaValues::DiscardBeforeCursor()
{
if (Cursor <= 0) return;
int Discard = Cursor;
if (Discard > Types.Num()) Discard = Types.Num();
Types.RemoveAt(0, Discard);
Data.RemoveAt(0, Discard);
Cursor -= Discard;
}
ElxLuaValueType UlxLuaValues::NextType() const ElxLuaValueType UlxLuaValues::NextType() const
{ {
if (Cursor < 0) return ElxLuaValueType::End; if (Cursor < 0) return ElxLuaValueType::End;
@@ -474,78 +458,90 @@ ElxLuaValueType UlxLuaValues::NextType() const
return Types[Cursor]; return Types[Cursor];
} }
void UlxLuaValues::ReadString(ElxSuccessOrWrongType &Status, FString &Result, bool LogErrorOnMismatch) void UlxLuaValues::DiscardBeforeCursor()
{ {
Status = CheckType(LogErrorOnMismatch, NextType(), ElxLuaValueType::String); if (Cursor > 0)
{
Types.RemoveAt(0, Cursor);
Data.RemoveAt(0, Cursor);
SavedCursor = FMath::Max(0, SavedCursor - Cursor);
Cursor = 0;
}
}
void UlxLuaValues::ReadString(ElxSuccessOrWrongType &Status, FString &Result)
{
Status = CheckType(NextType(), ElxLuaValueType::String);
if (Status == ElxSuccessOrWrongType::WrongType) if (Status == ElxSuccessOrWrongType::WrongType)
{ {
Result.Empty(); return; Cursor = SavedCursor; Result.Empty(); return;
} }
Result = FlxStreamBuffer(Data[Cursor++]).read_fstring(); Result = FlxStreamBuffer(Data[Cursor++]).read_fstring();
} }
void UlxLuaValues::ReadName(ElxSuccessOrWrongType &Status, FName &Result, bool LogErrorOnMismatch) void UlxLuaValues::ReadName(ElxSuccessOrWrongType &Status, FName &Result)
{ {
Status = CheckType(LogErrorOnMismatch, NextType(), ElxLuaValueType::Name); Status = CheckType(NextType(), ElxLuaValueType::Name);
if (Status == ElxSuccessOrWrongType::WrongType) if (Status == ElxSuccessOrWrongType::WrongType)
{ {
Result = FName(); return; Cursor = SavedCursor; Result = FName(); return;
} }
Result = FlxStreamBuffer(Data[Cursor++]).read_fname(); Result = FlxStreamBuffer(Data[Cursor++]).read_fname();
} }
void UlxLuaValues::ReadFloat(ElxSuccessOrWrongType &Status, double &Result, bool LogErrorOnMismatch) void UlxLuaValues::ReadFloat(ElxSuccessOrWrongType &Status, double &Result)
{ {
Status = CheckType(LogErrorOnMismatch, NextType(), ElxLuaValueType::Float); Status = CheckType(NextType(), ElxLuaValueType::Float);
if (Status == ElxSuccessOrWrongType::WrongType) if (Status == ElxSuccessOrWrongType::WrongType)
{ {
Result = 0.0; return; Cursor = SavedCursor; Result = 0.0; return;
} }
Result = FlxStreamBuffer(Data[Cursor++]).read_double(); Result = FlxStreamBuffer(Data[Cursor++]).read_double();
} }
void UlxLuaValues::ReadInt(ElxSuccessOrWrongType &Status, int &Result, bool LogErrorOnMismatch) void UlxLuaValues::ReadInt(ElxSuccessOrWrongType &Status, int &Result)
{ {
Status = CheckType(LogErrorOnMismatch, NextType(), ElxLuaValueType::Float); Status = CheckType(NextType(), ElxLuaValueType::Float);
if (Status == ElxSuccessOrWrongType::WrongType) if (Status == ElxSuccessOrWrongType::WrongType)
{ {
Result = 0.0; return; Cursor = SavedCursor; Result = 0; return;
} }
double dvalue = FlxStreamBuffer(Data[Cursor++]).read_double(); double dvalue = FlxStreamBuffer(Data[Cursor++]).read_double();
Result = int(dvalue); Result = int(dvalue);
if (double(Result) != dvalue) if (double(Result) != dvalue)
{ {
Result = 0; Status = ElxSuccessOrWrongType::WrongType; return; Status = ElxSuccessOrWrongType::WrongType;
Cursor = SavedCursor; Result = 0; return;
} }
} }
void UlxLuaValues::ReadVector(ElxSuccessOrWrongType &Status, FVector &Result, bool LogErrorOnMismatch) void UlxLuaValues::ReadVector(ElxSuccessOrWrongType &Status, FVector &Result)
{ {
Status = CheckType(LogErrorOnMismatch, NextType(), ElxLuaValueType::Vector); Status = CheckType(NextType(), ElxLuaValueType::Vector);
if (Status == ElxSuccessOrWrongType::WrongType) if (Status == ElxSuccessOrWrongType::WrongType)
{ {
Result = FVector(); return; Cursor = SavedCursor; Result = FVector(); return;
} }
Result = FlxStreamBuffer(Data[Cursor++]).read_fvector(); Result = FlxStreamBuffer(Data[Cursor++]).read_fvector();
} }
void UlxLuaValues::ReadVector2D(ElxSuccessOrWrongType &Status, FVector2D &Result, bool LogErrorOnMismatch) void UlxLuaValues::ReadVector2D(ElxSuccessOrWrongType &Status, FVector2D &Result)
{ {
Status = CheckType(LogErrorOnMismatch, NextType(), ElxLuaValueType::Vector); Status = CheckType(NextType(), ElxLuaValueType::Vector);
if (Status == ElxSuccessOrWrongType::WrongType) if (Status == ElxSuccessOrWrongType::WrongType)
{ {
Result = FVector2D(); return; Cursor = SavedCursor; Result = FVector2D(); return;
} }
FVector VValue = FlxStreamBuffer(Data[Cursor++]).read_fvector(); FVector VValue = FlxStreamBuffer(Data[Cursor++]).read_fvector();
Result = FVector2D(VValue.X, VValue.Y); Result = FVector2D(VValue.X, VValue.Y);
} }
void UlxLuaValues::ReadBoolean(ElxSuccessOrWrongType &Status, bool &Result, bool LogErrorOnMismatch) void UlxLuaValues::ReadBoolean(ElxSuccessOrWrongType &Status, bool &Result)
{ {
Status = CheckType(LogErrorOnMismatch, NextType(), ElxLuaValueType::Boolean); Status = CheckType(NextType(), ElxLuaValueType::Boolean);
if (Status == ElxSuccessOrWrongType::WrongType) if (Status == ElxSuccessOrWrongType::WrongType)
{ {
Result = false; return; Cursor = SavedCursor; Result = false; return;
} }
Result = FlxStreamBuffer(Data[Cursor++]).read_bool(); Result = FlxStreamBuffer(Data[Cursor++]).read_bool();
} }

View File

@@ -227,74 +227,116 @@ private:
// The current cursor. // The current cursor.
// //
int Cursor; int Cursor = 0;
// Saved cursor for rollback on error.
//
int SavedCursor = 0;
private: private:
// Clear everything. // Clear everything.
// //
void Empty(); void Empty();
// Compare two types. If they aren't equal, // Compare two types for equality.
// possibly log an error, and return a status
// code.
// //
static ElxSuccessOrWrongType CheckType(bool LogErrorOnWrongType, ElxLuaValueType Type, ElxLuaValueType Desired); static ElxSuccessOrWrongType CheckType(ElxLuaValueType Type, ElxLuaValueType Desired);
public: public:
UlxLuaValues() { Cursor = 0; } UlxLuaValues() {}
// Copies the data, then preprocesses it to make // Copies the data, then preprocesses it to make
// sure it's all valid. Returns false if invalid. // sure it's all valid. Returns false if invalid.
// //
bool Initialize(std::string_view data); bool Initialize(std::string_view data);
UFUNCTION(BlueprintCallable, Category = "Luprex|Lua Value Array") // Remove all elements before the cursor.
//
void DiscardBeforeCursor(); void DiscardBeforeCursor();
// Convert a lua values array into a string.
// The string shows all the values, including those
// before the cursor, and the cursor as well.
//
UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array") UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array")
FString DebugString() const; FString DebugString() const;
// Return the number of elements remaining after the cursor.
//
UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array") UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array")
int Length() const { return Types.Num(); } int Length() const { return Types.Num() - Cursor; }
// Return the total number of elements, including both those
// before and after the cursor.
//
UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array")
int TotalLength() const { return Types.Num(); }
// Get the current position of the cursor.
//
UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array") UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array")
int GetCursor() const { return Cursor; } int GetCursor() const { return Cursor; }
// Set the position of the cursor. The value is clamped to
// be within the array.
//
UFUNCTION(BlueprintCallable, Category = "Luprex|Lua Value Array") UFUNCTION(BlueprintCallable, Category = "Luprex|Lua Value Array")
void SetCursor(int n) { Cursor = n; } void SetCursor(int n) { Cursor = FMath::Clamp(n, 0, Types.Num()); }
// Get the type of the next element.
//
UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array") UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array")
ElxLuaValueType NextType() const; ElxLuaValueType NextType() const;
// Return true if the type of the next element is what you say.
//
UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array") UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array")
bool IsNextType(ElxLuaValueType Type) const { return NextType() == Type; } bool IsNextType(ElxLuaValueType Type) const { return NextType() == Type; }
// Switch-statement that depends on the type of the next element.
//
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "ReturnValue"), Category = "Luprex|Lua Value Array") UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "ReturnValue"), Category = "Luprex|Lua Value Array")
ElxLuaValueType SwitchNextType() { return NextType(); } ElxLuaValueType SwitchNextType() { return NextType(); }
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
void ReadString(ElxSuccessOrWrongType &Status, FString &Result, bool LogErrorOnWrongType = false);
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
void ReadName(ElxSuccessOrWrongType &Status, FName &Result, bool LogErrorOnWrongType = false);
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
void ReadFloat(ElxSuccessOrWrongType &Status, double &Result, bool LogErrorOnWrongType = false);
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
void ReadInt(ElxSuccessOrWrongType &Status, int &Result, bool LogErrorOnWrongType = false);
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
void ReadVector(ElxSuccessOrWrongType &Status, FVector &Result, bool LogErrorOnWrongType = false);
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
void ReadVector2D(ElxSuccessOrWrongType &Status, FVector2D &Result, bool LogErrorOnWrongType = false);
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
void ReadBoolean(ElxSuccessOrWrongType &Status, bool &Result, bool LogErrorOnWrongType = false);
// Allows you to pass a LuaValues to Format Message. // Allows you to pass a LuaValues to Format Message.
// //
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Lua Value Array") UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Lua Value Array")
static FFormatArgumentData FormatArgumentDataLuaValues(const UlxLuaValues *AutoConvertedValue, const FString &Name); static FFormatArgumentData FormatArgumentDataLuaValues(const UlxLuaValues *AutoConvertedValue, const FString &Name);
////////////////////////////////////////////////////////
//
// Routines that are used by the ReadLuaValues K2Node
// to read values out.
//
////////////////////////////////////////////////////////
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
void ReadString(ElxSuccessOrWrongType &Status, FString &Result);
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
void ReadName(ElxSuccessOrWrongType &Status, FName &Result);
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
void ReadFloat(ElxSuccessOrWrongType &Status, double &Result);
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
void ReadInt(ElxSuccessOrWrongType &Status, int &Result);
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
void ReadVector(ElxSuccessOrWrongType &Status, FVector &Result);
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
void ReadVector2D(ElxSuccessOrWrongType &Status, FVector2D &Result);
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
void ReadBoolean(ElxSuccessOrWrongType &Status, bool &Result);
////////////////////////////////////////////////////////
//
// Other routines used by the ReadLuaValues K2Node
//
////////////////////////////////////////////////////////
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Lua Value Array")
UlxLuaValues* SaveCursor() { SavedCursor = Cursor; return this; }
}; };

View File

@@ -232,15 +232,11 @@ void UK2Node_LuaInvoke::ExpandNode(class FKismetCompilerContext& CompilerContext
ThenPin = ChainExecPin(ThenPin, UnpackNode, TEXT("Success")); ThenPin = ChainExecPin(ThenPin, UnpackNode, TEXT("Success"));
} }
// If there is an extra results pin, hook it up. // If there is an extra results pin, pass through the LuaValues object.
if (ParsedProto.ExtraReturnValues) if (ParsedProto.ExtraReturnValues)
{ {
UEdGraphPin *ExtraPin = FindPinChecked(ExtraResultsPinName); UEdGraphPin *ExtraResultsPin = FindPinChecked(ExtraResultsPinName);
UFunction *DiscardFunc = UlxLuaValues::StaticClass()->FindFunctionByName(TEXT("DiscardBeforeCursor")); CompilerContext.MovePinLinksToIntermediate(*ExtraResultsPin, *ReturnArrayPin);
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);
} }
} }

View File

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

View File

@@ -1,3 +1,12 @@
////////////////////////////////////////////////////////////
//
// ReadLuaValues.h
//
// K2Node that reads typed values from a UlxLuaValues array.
// Takes a prototype string like "string x, float y, int z"
// and creates output pins with the appropriate types.
//
////////////////////////////////////////////////////////////
#pragma once #pragma once
@@ -10,10 +19,6 @@ class FString;
class UEdGraph; class UEdGraph;
class UObject; class UObject;
//
// The Lua Call K2Node.
//
UCLASS(MinimalAPI) UCLASS(MinimalAPI)
class UK2Node_ReadLuaValues : public UlxK2Node class UK2Node_ReadLuaValues : public UlxK2Node
{ {
@@ -42,24 +47,20 @@ public:
//~ End UK2Node Interface. //~ End UK2Node Interface.
private: private:
static const FName PrototypePinName;
virtual bool IsInvoke() const { return true; } static const FName InputValuesPinName;
static const FName RemainingPinName;
/** Pin Names for the three built-in Pins **/
static const FName FunctionPinName;
static const FName PlacePinName;
static const FName ExtraResultsPinName;
static const FName ErrorPinName; static const FName ErrorPinName;
private: private:
// Whenever the function value changes, we call // Whenever the prototype pin value changes, we call
// ReconstructNode, which backs up the value into this // ReconstructNode, which backs up the value into this
// property. This cache is needed because during // property. This cache is needed because during
// ReconstructNode, we blow away the function pin. The // ReconstructNode, we blow away the prototype pin. The
// function pin is also absent when the node is first // prototype pin is also absent when the node is first
// created. // created.
// //
UPROPERTY() UPROPERTY()
FString LuaFunctionPrototype = TEXT("class.func(int arg1, int arg2) : int ret1, int ret2"); FString ValuePrototype = TEXT("string x, float y, int z");
}; };