From 6d018fc02b0611ede478e952df2630c5b8fb5e7f Mon Sep 17 00:00:00 2001 From: jyelon Date: Wed, 4 Mar 2026 19:54:13 -0500 Subject: [PATCH] Finished implementing the ReadLuaValues K2Node --- Content/Widgets/WB_Hotkeys.uasset | 4 +- Source/Integration/LuaCall.cpp | 80 +++++----- Source/Integration/LuaCall.h | 104 +++++++++---- Source/Integration/LuaCallNode.cpp | 10 +- Source/Integration/ReadLuaValues.cpp | 220 ++++++++------------------- Source/Integration/ReadLuaValues.h | 31 ++-- 6 files changed, 199 insertions(+), 250 deletions(-) diff --git a/Content/Widgets/WB_Hotkeys.uasset b/Content/Widgets/WB_Hotkeys.uasset index 04c0a2dc..4a1635c8 100644 --- a/Content/Widgets/WB_Hotkeys.uasset +++ b/Content/Widgets/WB_Hotkeys.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:689cfca7f6ee1cddf32585eb69a014762da47ae9b79434bb4b8034fb71ddcf0b -size 260600 +oid sha256:3465925755914514d8ef8d29f672ce396123fdf79928ae2d94f276b34aea565a +size 253351 diff --git a/Source/Integration/LuaCall.cpp b/Source/Integration/LuaCall.cpp index ff3b76e4..6f09e0ef 100644 --- a/Source/Integration/LuaCall.cpp +++ b/Source/Integration/LuaCall.cpp @@ -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()->GetDisplayNameTextByValue(int64(Type)).ToString(); - FString DesiredName = StaticEnum()->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(); } diff --git a/Source/Integration/LuaCall.h b/Source/Integration/LuaCall.h index 0af07448..db6a4e5e 100644 --- a/Source/Integration/LuaCall.h +++ b/Source/Integration/LuaCall.h @@ -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") - void DiscardBeforeCursor(); + // 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; } }; diff --git a/Source/Integration/LuaCallNode.cpp b/Source/Integration/LuaCallNode.cpp index 899c872a..6aaa6a53 100644 --- a/Source/Integration/LuaCallNode.cpp +++ b/Source/Integration/LuaCallNode.cpp @@ -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); } } diff --git a/Source/Integration/ReadLuaValues.cpp b/Source/Integration/ReadLuaValues.cpp index 6ac12c32..72a3cba8 100644 --- a/Source/Integration/ReadLuaValues.cpp +++ b/Source/Integration/ReadLuaValues.cpp @@ -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; } diff --git a/Source/Integration/ReadLuaValues.h b/Source/Integration/ReadLuaValues.h index c7e2d3ae..d094522c 100644 --- a/Source/Integration/ReadLuaValues.h +++ b/Source/Integration/ReadLuaValues.h @@ -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"); };