From 865297331a75f2a8469f75d9541c2be703a17e50 Mon Sep 17 00:00:00 2001 From: jyelon Date: Mon, 7 Apr 2025 18:00:45 -0400 Subject: [PATCH] Better error handling in 'Call a Lua Function' k2node --- Content/Luprex/lxGameMode.uasset | 4 +- Source/Integration/LuaCall.cpp | 18 +++++++- Source/Integration/LuaCall.h | 4 +- Source/Integration/LuaCallNode.cpp | 71 +++++++++++++++++++++--------- Source/Integration/LuaCallNode.h | 6 +++ luprex/cpp/core/world-core.cpp | 16 ++++++- 6 files changed, 90 insertions(+), 29 deletions(-) diff --git a/Content/Luprex/lxGameMode.uasset b/Content/Luprex/lxGameMode.uasset index 1b382459..7c9182bf 100644 --- a/Content/Luprex/lxGameMode.uasset +++ b/Content/Luprex/lxGameMode.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:615e0ffa732710680a3b3ed3cb35a391f49f965c5d8a49f82e03f6dfb7577df8 -size 161973 +oid sha256:998665157f72c0b5d2cbaf2b68e99c23e082af4a7ae15de58ad447a7c8aaf001 +size 160478 diff --git a/Source/Integration/LuaCall.cpp b/Source/Integration/LuaCall.cpp index ff12352a..83e42fd9 100644 --- a/Source/Integration/LuaCall.cpp +++ b/Source/Integration/LuaCall.cpp @@ -223,12 +223,26 @@ void UlxLuaCallLibrary::LuaCallInvoke(UObject *context, AActor *place) } -void UlxLuaCallLibrary::LuaCallProbe(UObject *context, AActor *place) +bool UlxLuaCallLibrary::LuaCallProbe(UObject *context, AActor *place) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); - if (NotInitialized(sb)) return; + if (NotInitialized(sb)) return false; mode->LuaCallEnd(InvocationKind::LUA_PROBE, place); + FlxStreamBuffer &result = mode->LuaCallGetResult(); + SimpleDynamicTag tag = result.read_simple_dynamic_tag(); + if (tag != SimpleDynamicTag::STRING) + { + UE_LOG(LogLuprexIntegration, Error, TEXT("corruption in lua_probe")); + return false; + } + FString ErrorMessage = result.read_fstring(); + if (!ErrorMessage.IsEmpty()) + { + UE_LOG(LogLuprex, Error, TEXT("%s"), *ErrorMessage); + return false; + } + return true; } UlxLuaValues *UlxLuaCallLibrary::LuaCallGetRest(UObject *context) diff --git a/Source/Integration/LuaCall.h b/Source/Integration/LuaCall.h index 339ccfb7..db7541c5 100644 --- a/Source/Integration/LuaCall.h +++ b/Source/Integration/LuaCall.h @@ -118,8 +118,8 @@ public: UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function") static void LuaCallInvoke(UObject *context, AActor *Place); - UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function") - static void LuaCallProbe(UObject *context, AActor *Place); + UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", ExpandBoolAsExecs="ReturnValue", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function") + static bool LuaCallProbe(UObject *context, AActor *Place); UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function") static UlxLuaValues *LuaCallGetRest(UObject *context); diff --git a/Source/Integration/LuaCallNode.cpp b/Source/Integration/LuaCallNode.cpp index ed81b1e9..05a3214f 100644 --- a/Source/Integration/LuaCallNode.cpp +++ b/Source/Integration/LuaCallNode.cpp @@ -49,6 +49,7 @@ const FName UK2Node_LuaCall::FunctionPinName(TEXT("Lua Function Prototype")); const FName UK2Node_LuaCall::InvokeOrProbePinName(TEXT("Invoke or Probe")); const FName UK2Node_LuaCall::PlacePinName(TEXT("Place Tangible")); const FName UK2Node_LuaCall::ExtraResultsPinName(TEXT("Extra Results")); +const FName UK2Node_LuaCall::ErrorPinName(TEXT("Lua Error")); bool UK2Node_LuaCall::IsPrefix(const UEdGraphPin *Pin, char Prefix) { @@ -131,6 +132,8 @@ void UK2Node_LuaCall::AllocateDefaultPins() void UK2Node_LuaCall::CreateCorrectPins() { + UEnum *IPEnum = StaticEnum(); + if (LuaFunctionPrototype.IsEmpty()) { LuaFunctionPrototype = TEXT("class.func(int arg1, int arg2) : int ret1, int ret2"); @@ -177,17 +180,22 @@ void UK2Node_LuaCall::CreateCorrectPins() CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then); } + if (InvokeOrProbe == TEXT("Probe")) + { + if (!KeepPin(ErrorPinName)) + { + CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, ErrorPinName); + } + } + if (!KeepPin(FunctionPinName)) { UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FunctionPinName); - P->DefaultValue = LuaFunctionPrototype; } if (!KeepPin(InvokeOrProbePinName)) { - UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum(), InvokeOrProbePinName); - P->DefaultValue = TEXT("Probe"); - P->AutogeneratedDefaultValue = P->DefaultValue; + UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, IPEnum, InvokeOrProbePinName); } if (!KeepPin(PlacePinName)) @@ -195,6 +203,12 @@ void UK2Node_LuaCall::CreateCorrectPins() UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, AActor::StaticClass(), PlacePinName); } + // Copy the property names into the pins. + UEdGraphPin *FunctionPin = FindPinChecked(FunctionPinName); + UEdGraphPin *InvokeOrProbePin = FindPinChecked(InvokeOrProbePinName); + FunctionPin->DefaultValue = LuaFunctionPrototype; + InvokeOrProbePin->DefaultValue = InvokeOrProbe; + // Create Argument pins in the correct order, reusing old pins where possible. for (const FlxParsedProto::Pin & Pin : ParsedProto.Arguments) { @@ -257,7 +271,7 @@ FText UK2Node_LuaCall::GetNodeTitle(ENodeTitleType::Type TitleType) const FText UK2Node_LuaCall::GetPinDisplayName(const UEdGraphPin* Pin) const { // The exec pins don't need labels. - if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) + if ((Pin->PinName == UEdGraphSchema_K2::PN_Execute) || (Pin->PinName == UEdGraphSchema_K2::PN_Then)) { return FText::GetEmpty(); } @@ -306,6 +320,12 @@ void UK2Node_LuaCall::PinDefaultValueChanged(UEdGraphPin* Pin) CreateCorrectPins(); GetGraph()->NotifyNodeChanged(this); } + else if (Pin->PinName == InvokeOrProbePinName) + { + InvokeOrProbe = Pin->DefaultValue; + CreateCorrectPins(); + GetGraph()->NotifyNodeChanged(this); + } } FText UK2Node_LuaCall::GetTooltipText() const @@ -342,8 +362,8 @@ void UK2Node_LuaCall::ExpandNode(class FKismetCompilerContext& CompilerContext, UK2Node_CallFunction* BeginNode = MakeCallFunctionNode(LuaCallLibraryFunction(LuaCallBegin)); Schema->TrySetDefaultValue(*BeginNode->FindPinChecked(TEXT("ClassName")), ParsedProto.ClassName); Schema->TrySetDefaultValue(*BeginNode->FindPinChecked(TEXT("FunctionName")), ParsedProto.FunctionName); - UK2Node_CallFunction* PrevNode = BeginNode; - + UEdGraphPin *ThenPin = BeginNode->GetThenPin(); + // Add Packing operations for all argument pins. for (const FlxParsedProto::Pin &PinInfo : ParsedProto.Arguments) { @@ -357,17 +377,26 @@ void UK2Node_LuaCall::ExpandNode(class FKismetCompilerContext& CompilerContext, } UK2Node_CallFunction *PackNode = MakeCallFunctionNode(PackingFunc); CompilerContext.MovePinLinksToIntermediate(*Pin, *PackNode->FindPinChecked(TEXT("Value"))); - PrevNode->GetThenPin()->MakeLinkTo(PackNode->GetExecPin()); - PrevNode = PackNode; + ThenPin->MakeLinkTo(PackNode->GetExecPin()); + ThenPin = PackNode->GetThenPin(); } // Add the invoke or probe node. - bool IsInvoke = (FindPinChecked(InvokeOrProbePinName, EGPD_Input)->DefaultValue == TEXT("Invoke")); - UFunction *Action = IsInvoke ? LuaCallLibraryFunction(LuaCallInvoke) : LuaCallLibraryFunction(LuaCallProbe); - UK2Node_CallFunction* ActionNode = MakeCallFunctionNode(Action); - CompilerContext.MovePinLinksToIntermediate(*FindPin(PlacePinName, EGPD_Input), *ActionNode->FindPinChecked(TEXT("Place"))); - PrevNode->GetThenPin()->MakeLinkTo(ActionNode->GetExecPin()); - PrevNode = ActionNode; + if (InvokeOrProbe == TEXT("Probe")) + { + UK2Node_CallFunction* ActionNode = MakeCallFunctionNode(LuaCallLibraryFunction(LuaCallProbe)); + CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(PlacePinName, EGPD_Input), *ActionNode->FindPinChecked(TEXT("Place"))); + CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(ErrorPinName, EGPD_Output), *ActionNode->FindPinChecked(TEXT("False"))); + ThenPin->MakeLinkTo(ActionNode->GetExecPin()); + ThenPin = ActionNode->FindPinChecked(TEXT("True")); + } + else + { + UK2Node_CallFunction* ActionNode = MakeCallFunctionNode(LuaCallLibraryFunction(LuaCallInvoke)); + CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(PlacePinName, EGPD_Input), *ActionNode->FindPinChecked(TEXT("Place"))); + ThenPin->MakeLinkTo(ActionNode->GetExecPin()); + ThenPin = ActionNode->GetThenPin(); + } // Add Unpacking operations for all return value pins. for (const FlxParsedProto::Pin &PinInfo : ParsedProto.ReturnValues) @@ -382,8 +411,8 @@ void UK2Node_LuaCall::ExpandNode(class FKismetCompilerContext& CompilerContext, } UK2Node_CallFunction *UnpackNode = MakeCallFunctionNode(UnpackingFunc); CompilerContext.MovePinLinksToIntermediate(*Pin, *UnpackNode->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue)); - PrevNode->GetThenPin()->MakeLinkTo(UnpackNode->GetExecPin()); - PrevNode = UnpackNode; + ThenPin->MakeLinkTo(UnpackNode->GetExecPin()); + ThenPin = UnpackNode->GetThenPin(); } // If there is an extra results pin, hook it up. @@ -392,16 +421,16 @@ void UK2Node_LuaCall::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraphPin *Pin = FindPinChecked(ExtraResultsPinName); UK2Node_CallFunction *UnpackNode = MakeCallFunctionNode(LuaCallLibraryFunction(LuaCallGetRest)); CompilerContext.MovePinLinksToIntermediate(*Pin, *UnpackNode->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue)); - PrevNode->GetThenPin()->MakeLinkTo(UnpackNode->GetExecPin()); - PrevNode = UnpackNode; + ThenPin->MakeLinkTo(UnpackNode->GetExecPin()); + ThenPin = UnpackNode->GetThenPin(); } // Link up the Exec pins. CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *BeginNode->GetExecPin()); - CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *PrevNode->GetThenPin()); + CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *ThenPin); // Make sure we didn't have return values for an invoke. - if (IsInvoke && ((ParsedProto.ReturnValues.Num() > 0) || (ParsedProto.ExtraReturnValues))) + if ((InvokeOrProbe != TEXT("Probe")) && ((ParsedProto.ReturnValues.Num() > 0) || (ParsedProto.ExtraReturnValues))) { CompilerContext.MessageLog.Error(TEXT("Lua Call in Invoke mode does not support return values")); } diff --git a/Source/Integration/LuaCallNode.h b/Source/Integration/LuaCallNode.h index aeb1fc08..21b2f8f7 100644 --- a/Source/Integration/LuaCallNode.h +++ b/Source/Integration/LuaCallNode.h @@ -78,6 +78,7 @@ private: static const FName InvokeOrProbePinName; static const FName PlacePinName; static const FName ExtraResultsPinName; + static const FName ErrorPinName; /** Utility functions for manipulating pin names with prefixes **/ static bool IsPrefix(const UEdGraphPin *Pin, char Prefix); @@ -88,6 +89,11 @@ private: /** The lua function prototype, which must be saved as a property **/ UPROPERTY() FString LuaFunctionPrototype; + + /** Equal to the invoke-or-probe property **/ + UPROPERTY() + FString InvokeOrProbe; + /** Tooltip text for this node. */ FText NodeTooltip; diff --git a/luprex/cpp/core/world-core.cpp b/luprex/cpp/core/world-core.cpp index 18c50c44..f96a1a02 100644 --- a/luprex/cpp/core/world-core.cpp +++ b/luprex/cpp/core/world-core.cpp @@ -491,6 +491,14 @@ void World::probe_lua_call(int64_t actor_id, int64_t place_id, std::string_view lthread_prints_.reset(); util::dprint(prints); + // If a probe generates a lua error, we're not supposed to dprint it. + // Instead, we're supposed to send it back to unreal as the first + // return value of the function. + // + int64_t rv_base = retvals->total_writes(); + retvals->write_simple_dynamic_tag(SimpleDynamicTag::STRING); + retvals->write_string(msg); + if (msg.empty()) { bool ok = true; for (int i = 0; ok && (i < returnvalues.size()); i++) { @@ -514,8 +522,12 @@ void World::probe_lua_call(int64_t actor_id, int64_t place_id, std::string_view ok = encode_simple_dynamic(LS, retvec, retvals); } } - } else { - util::dprint(msg); + if (!ok) { + msg = util::ss("Lua function ",classname,".",funcname," returned a non-serializable value"); + retvals->unwrite_to(rv_base); + retvals->write_simple_dynamic_tag(SimpleDynamicTag::STRING); + retvals->write_string(msg); + } } close_lthread_state();