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;
FString ErrorMessage;
ReturnArray->ReadString(Status, ErrorMessage, false);
ReturnArray->ReadString(Status, ErrorMessage);
if (Status != ElxSuccessOrWrongType::Success)
{
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;
return false;
}
ReturnArray->DiscardBeforeCursor();
return true;
}
@@ -383,7 +384,6 @@ bool UlxLuaValues::Initialize(std::string_view data)
while (!Decoder.empty())
{
LuaValueType Tag = Decoder.read_lua_value_type();
int64 Pos = Decoder.total_reads();
ElxLuaValueType Type;
switch (Tag)
@@ -411,7 +411,7 @@ FString UlxLuaValues::DebugString() const
Output << TEXT("{ ");
for (int i = 0; i < Types.Num(); i++)
{
if (i > 0) Output << TEXT(", ");
if (i > 0) Output << TEXT(" ");
if (i == Cursor) Output << TEXT("^ ");
@@ -442,31 +442,15 @@ FString UlxLuaValues::DebugString() const
return Output.ToString();
}
ElxSuccessOrWrongType UlxLuaValues::CheckType(bool LogErrorOnWrongType, ElxLuaValueType Type, ElxLuaValueType Desired)
ElxSuccessOrWrongType UlxLuaValues::CheckType(ElxLuaValueType Type, ElxLuaValueType 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::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
{
if (Cursor < 0) return ElxLuaValueType::End;
@@ -474,78 +458,90 @@ ElxLuaValueType UlxLuaValues::NextType() const
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)
{
Result.Empty(); return;
Cursor = SavedCursor; Result.Empty(); return;
}
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)
{
Result = FName(); return;
Cursor = SavedCursor; Result = FName(); return;
}
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)
{
Result = 0.0; return;
Cursor = SavedCursor; Result = 0.0; return;
}
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)
{
Result = 0.0; return;
Cursor = SavedCursor; Result = 0; return;
}
double dvalue = FlxStreamBuffer(Data[Cursor++]).read_double();
Result = int(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)
{
Result = FVector(); return;
Cursor = SavedCursor; Result = FVector(); return;
}
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)
{
Result = FVector2D(); return;
Cursor = SavedCursor; Result = FVector2D(); return;
}
FVector VValue = FlxStreamBuffer(Data[Cursor++]).read_fvector();
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)
{
Result = false; return;
Cursor = SavedCursor; Result = false; return;
}
Result = FlxStreamBuffer(Data[Cursor++]).read_bool();
}

View File

@@ -227,74 +227,116 @@ private:
// The current cursor.
//
int Cursor;
int Cursor = 0;
// Saved cursor for rollback on error.
//
int SavedCursor = 0;
private:
// Clear everything.
//
void Empty();
// Compare two types. If they aren't equal,
// possibly log an error, and return a status
// code.
// Compare two types for equality.
//
static ElxSuccessOrWrongType CheckType(bool LogErrorOnWrongType, ElxLuaValueType Type, ElxLuaValueType Desired);
static ElxSuccessOrWrongType CheckType(ElxLuaValueType Type, ElxLuaValueType Desired);
public:
UlxLuaValues() { Cursor = 0; }
UlxLuaValues() {}
// Copies the data, then preprocesses it to make
// sure it's all valid. Returns false if invalid.
//
bool Initialize(std::string_view data);
UFUNCTION(BlueprintCallable, Category = "Luprex|Lua Value Array")
// Remove all elements before the cursor.
//
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")
FString DebugString() const;
// Return the number of elements remaining after the cursor.
//
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")
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")
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")
ElxLuaValueType NextType() const;
// Return true if the type of the next element is what you say.
//
UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array")
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")
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.
//
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Lua Value Array")
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"));
}
// 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)
{
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 *ExtraResultsPin = FindPinChecked(ExtraResultsPinName);
CompilerContext.MovePinLinksToIntermediate(*ExtraResultsPin, *ReturnArrayPin);
}
}

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);
if (!IsInvoke())
{
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, ErrorPinName);
}
UEdGraphPin *FunctionPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FunctionPinName);
FunctionPin->DefaultValue = LuaFunctionPrototype;
UEdGraphPin *PrototypePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, PrototypePinName);
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.
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,116 +96,73 @@ 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)
{
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"));
}
FlxParsedProto ParsedProto = FlxParsedProto::ParseReturnValuesOnly(ValuePrototype);
UEdGraphPin *InputInputValuesCopyPin = FindPinChecked(InputValuesPinName);
// Add the invoke or probe node.
UEdGraphPin *ReturnArrayPin = nullptr;
if (IsInvoke())
{
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"));
}
// 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();
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.
// 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, '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."));
CompilerContext.MessageLog.Error(TEXT("All 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")));
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"));
}
// 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)
{
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;
}

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
@@ -10,10 +19,6 @@ class FString;
class UEdGraph;
class UObject;
//
// The Lua Call K2Node.
//
UCLASS(MinimalAPI)
class UK2Node_ReadLuaValues : public UlxK2Node
{
@@ -42,24 +47,20 @@ public:
//~ End UK2Node Interface.
private:
virtual bool IsInvoke() const { return true; }
/** Pin Names for the three built-in Pins **/
static const FName FunctionPinName;
static const FName PlacePinName;
static const FName ExtraResultsPinName;
static const FName PrototypePinName;
static const FName InputValuesPinName;
static const FName RemainingPinName;
static const FName ErrorPinName;
private:
// Whenever the function value changes, we call
// Whenever the prototype pin 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
// ReconstructNode, we blow away the prototype pin. The
// prototype pin is also absent when the node is first
// created.
//
UPROPERTY()
FString LuaFunctionPrototype = TEXT("class.func(int arg1, int arg2) : int ret1, int ret2");
FString ValuePrototype = TEXT("string x, float y, int z");
};