Better error handling in 'Call a Lua Function' k2node

This commit is contained in:
2025-04-07 18:00:45 -04:00
parent d35125eb70
commit 865297331a
6 changed files with 90 additions and 29 deletions

Binary file not shown.

View File

@@ -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); ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
if (NotInitialized(sb)) return; if (NotInitialized(sb)) return false;
mode->LuaCallEnd(InvocationKind::LUA_PROBE, place); 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) UlxLuaValues *UlxLuaCallLibrary::LuaCallGetRest(UObject *context)

View File

@@ -118,8 +118,8 @@ public:
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function") UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function")
static void LuaCallInvoke(UObject *context, AActor *Place); static void LuaCallInvoke(UObject *context, AActor *Place);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function") UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", ExpandBoolAsExecs="ReturnValue", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function")
static void LuaCallProbe(UObject *context, AActor *Place); static bool LuaCallProbe(UObject *context, AActor *Place);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function") UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function")
static UlxLuaValues *LuaCallGetRest(UObject *context); static UlxLuaValues *LuaCallGetRest(UObject *context);

View File

@@ -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::InvokeOrProbePinName(TEXT("Invoke or Probe"));
const FName UK2Node_LuaCall::PlacePinName(TEXT("Place Tangible")); const FName UK2Node_LuaCall::PlacePinName(TEXT("Place Tangible"));
const FName UK2Node_LuaCall::ExtraResultsPinName(TEXT("Extra Results")); 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) bool UK2Node_LuaCall::IsPrefix(const UEdGraphPin *Pin, char Prefix)
{ {
@@ -131,6 +132,8 @@ void UK2Node_LuaCall::AllocateDefaultPins()
void UK2Node_LuaCall::CreateCorrectPins() void UK2Node_LuaCall::CreateCorrectPins()
{ {
UEnum *IPEnum = StaticEnum<ElxInvokeOrProbe>();
if (LuaFunctionPrototype.IsEmpty()) if (LuaFunctionPrototype.IsEmpty())
{ {
LuaFunctionPrototype = TEXT("class.func(int arg1, int arg2) : int ret1, int ret2"); 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); 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)) if (!KeepPin(FunctionPinName))
{ {
UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FunctionPinName); UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FunctionPinName);
P->DefaultValue = LuaFunctionPrototype;
} }
if (!KeepPin(InvokeOrProbePinName)) if (!KeepPin(InvokeOrProbePinName))
{ {
UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum<ElxInvokeOrProbe>(), InvokeOrProbePinName); UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, IPEnum, InvokeOrProbePinName);
P->DefaultValue = TEXT("Probe");
P->AutogeneratedDefaultValue = P->DefaultValue;
} }
if (!KeepPin(PlacePinName)) if (!KeepPin(PlacePinName))
@@ -195,6 +203,12 @@ void UK2Node_LuaCall::CreateCorrectPins()
UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, AActor::StaticClass(), PlacePinName); 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. // Create Argument pins in the correct order, reusing old pins where possible.
for (const FlxParsedProto::Pin & Pin : ParsedProto.Arguments) 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 FText UK2Node_LuaCall::GetPinDisplayName(const UEdGraphPin* Pin) const
{ {
// The exec pins don't need labels. // 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(); return FText::GetEmpty();
} }
@@ -306,6 +320,12 @@ void UK2Node_LuaCall::PinDefaultValueChanged(UEdGraphPin* Pin)
CreateCorrectPins(); CreateCorrectPins();
GetGraph()->NotifyNodeChanged(this); GetGraph()->NotifyNodeChanged(this);
} }
else if (Pin->PinName == InvokeOrProbePinName)
{
InvokeOrProbe = Pin->DefaultValue;
CreateCorrectPins();
GetGraph()->NotifyNodeChanged(this);
}
} }
FText UK2Node_LuaCall::GetTooltipText() const FText UK2Node_LuaCall::GetTooltipText() const
@@ -342,7 +362,7 @@ void UK2Node_LuaCall::ExpandNode(class FKismetCompilerContext& CompilerContext,
UK2Node_CallFunction* BeginNode = MakeCallFunctionNode(LuaCallLibraryFunction(LuaCallBegin)); UK2Node_CallFunction* BeginNode = MakeCallFunctionNode(LuaCallLibraryFunction(LuaCallBegin));
Schema->TrySetDefaultValue(*BeginNode->FindPinChecked(TEXT("ClassName")), ParsedProto.ClassName); Schema->TrySetDefaultValue(*BeginNode->FindPinChecked(TEXT("ClassName")), ParsedProto.ClassName);
Schema->TrySetDefaultValue(*BeginNode->FindPinChecked(TEXT("FunctionName")), ParsedProto.FunctionName); Schema->TrySetDefaultValue(*BeginNode->FindPinChecked(TEXT("FunctionName")), ParsedProto.FunctionName);
UK2Node_CallFunction* PrevNode = BeginNode; UEdGraphPin *ThenPin = BeginNode->GetThenPin();
// Add Packing operations for all argument pins. // Add Packing operations for all argument pins.
for (const FlxParsedProto::Pin &PinInfo : ParsedProto.Arguments) for (const FlxParsedProto::Pin &PinInfo : ParsedProto.Arguments)
@@ -357,17 +377,26 @@ void UK2Node_LuaCall::ExpandNode(class FKismetCompilerContext& CompilerContext,
} }
UK2Node_CallFunction *PackNode = MakeCallFunctionNode(PackingFunc); UK2Node_CallFunction *PackNode = MakeCallFunctionNode(PackingFunc);
CompilerContext.MovePinLinksToIntermediate(*Pin, *PackNode->FindPinChecked(TEXT("Value"))); CompilerContext.MovePinLinksToIntermediate(*Pin, *PackNode->FindPinChecked(TEXT("Value")));
PrevNode->GetThenPin()->MakeLinkTo(PackNode->GetExecPin()); ThenPin->MakeLinkTo(PackNode->GetExecPin());
PrevNode = PackNode; ThenPin = PackNode->GetThenPin();
} }
// Add the invoke or probe node. // Add the invoke or probe node.
bool IsInvoke = (FindPinChecked(InvokeOrProbePinName, EGPD_Input)->DefaultValue == TEXT("Invoke")); if (InvokeOrProbe == TEXT("Probe"))
UFunction *Action = IsInvoke ? LuaCallLibraryFunction(LuaCallInvoke) : LuaCallLibraryFunction(LuaCallProbe); {
UK2Node_CallFunction* ActionNode = MakeCallFunctionNode(Action); UK2Node_CallFunction* ActionNode = MakeCallFunctionNode(LuaCallLibraryFunction(LuaCallProbe));
CompilerContext.MovePinLinksToIntermediate(*FindPin(PlacePinName, EGPD_Input), *ActionNode->FindPinChecked(TEXT("Place"))); CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(PlacePinName, EGPD_Input), *ActionNode->FindPinChecked(TEXT("Place")));
PrevNode->GetThenPin()->MakeLinkTo(ActionNode->GetExecPin()); CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(ErrorPinName, EGPD_Output), *ActionNode->FindPinChecked(TEXT("False")));
PrevNode = ActionNode; 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. // Add Unpacking operations for all return value pins.
for (const FlxParsedProto::Pin &PinInfo : ParsedProto.ReturnValues) for (const FlxParsedProto::Pin &PinInfo : ParsedProto.ReturnValues)
@@ -382,8 +411,8 @@ void UK2Node_LuaCall::ExpandNode(class FKismetCompilerContext& CompilerContext,
} }
UK2Node_CallFunction *UnpackNode = MakeCallFunctionNode(UnpackingFunc); UK2Node_CallFunction *UnpackNode = MakeCallFunctionNode(UnpackingFunc);
CompilerContext.MovePinLinksToIntermediate(*Pin, *UnpackNode->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue)); CompilerContext.MovePinLinksToIntermediate(*Pin, *UnpackNode->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue));
PrevNode->GetThenPin()->MakeLinkTo(UnpackNode->GetExecPin()); ThenPin->MakeLinkTo(UnpackNode->GetExecPin());
PrevNode = UnpackNode; ThenPin = UnpackNode->GetThenPin();
} }
// If there is an extra results pin, hook it up. // 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); UEdGraphPin *Pin = FindPinChecked(ExtraResultsPinName);
UK2Node_CallFunction *UnpackNode = MakeCallFunctionNode(LuaCallLibraryFunction(LuaCallGetRest)); UK2Node_CallFunction *UnpackNode = MakeCallFunctionNode(LuaCallLibraryFunction(LuaCallGetRest));
CompilerContext.MovePinLinksToIntermediate(*Pin, *UnpackNode->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue)); CompilerContext.MovePinLinksToIntermediate(*Pin, *UnpackNode->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue));
PrevNode->GetThenPin()->MakeLinkTo(UnpackNode->GetExecPin()); ThenPin->MakeLinkTo(UnpackNode->GetExecPin());
PrevNode = UnpackNode; ThenPin = UnpackNode->GetThenPin();
} }
// Link up the Exec pins. // Link up the Exec pins.
CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *BeginNode->GetExecPin()); 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. // 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")); CompilerContext.MessageLog.Error(TEXT("Lua Call in Invoke mode does not support return values"));
} }

View File

@@ -78,6 +78,7 @@ private:
static const FName InvokeOrProbePinName; static const FName InvokeOrProbePinName;
static const FName PlacePinName; static const FName PlacePinName;
static const FName ExtraResultsPinName; static const FName ExtraResultsPinName;
static const FName ErrorPinName;
/** Utility functions for manipulating pin names with prefixes **/ /** Utility functions for manipulating pin names with prefixes **/
static bool IsPrefix(const UEdGraphPin *Pin, char Prefix); static bool IsPrefix(const UEdGraphPin *Pin, char Prefix);
@@ -89,6 +90,11 @@ private:
UPROPERTY() UPROPERTY()
FString LuaFunctionPrototype; FString LuaFunctionPrototype;
/** Equal to the invoke-or-probe property **/
UPROPERTY()
FString InvokeOrProbe;
/** Tooltip text for this node. */ /** Tooltip text for this node. */
FText NodeTooltip; FText NodeTooltip;
}; };

View File

@@ -491,6 +491,14 @@ void World::probe_lua_call(int64_t actor_id, int64_t place_id, std::string_view
lthread_prints_.reset(); lthread_prints_.reset();
util::dprint(prints); 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()) { if (msg.empty()) {
bool ok = true; bool ok = true;
for (int i = 0; ok && (i < returnvalues.size()); i++) { 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); ok = encode_simple_dynamic(LS, retvec, retvals);
} }
} }
} else { if (!ok) {
util::dprint(msg); 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(); close_lthread_state();