A lot of work on the Lua Call interface and some work on animation queues

This commit is contained in:
2025-02-26 14:47:53 -05:00
parent bed4f3e805
commit 72eda3026f
11 changed files with 530 additions and 409 deletions

View File

@@ -91,160 +91,43 @@ static bool IsPlacePin(const UEdGraphPin *Pin) {
return (Pin->PinName == PlacePinName);
}
// A parser for lua function prototypes.
//
struct FlxParsedProto {
FString ErrorMessage;
TArray<FString> Tokens;
int NextToken;
FString ClassName;
FString FunctionName;
TArray<FString> Arguments;
TArray<FString> ReturnValues;
bool ExtraReturnValues;
#define LuaCallLibraryFunction(name) (UlxLuaCallLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxLuaCallLibrary, name)))
// Check the next token to see if it's exactly equal to text.
//
bool IsLiteral(const TCHAR *text);
// Check the next token to see if it's an identifier.
//
bool IsIdent();
// Empty out the FlxParsedProto.
//
void Empty();
// Make a syntax error message, using the tokens.
//
void Syntax();
// Parse a function prototype.
//
void Parse(const FString &proto);
// Construct with a prototype.
//
FlxParsedProto(const FString &str) { Parse(str); }
};
bool FlxParsedProto::IsLiteral(const TCHAR *text) {
return ((NextToken < Tokens.Num()) && (Tokens[NextToken] == text));
static FEdGraphPinType GetPinType(const FProperty *Property)
{
FEdGraphPinType PinType;
bool IsWeak;
FName PinCat, PinSubCat;
UObject *PinSubCatObj = nullptr;
UEdGraphSchema_K2::GetPropertyCategoryInfo(Property, PinCat, PinSubCat, PinSubCatObj, IsWeak);
PinType.PinCategory = PinCat;
PinType.PinSubCategory = PinSubCat;
PinType.PinSubCategoryObject = PinSubCatObj;
return PinType;
}
bool FlxParsedProto::IsIdent() {
return ((NextToken < Tokens.Num()) && (FChar::IsAlpha(Tokens[NextToken][0])));
}
void FlxParsedProto::Empty() {
ErrorMessage = TEXT("");
Tokens.Empty();
NextToken = 0;
ClassName = TEXT("");
FunctionName = TEXT("");
Arguments.Empty();
ReturnValues.Empty();
ExtraReturnValues = false;
}
void FlxParsedProto::Syntax() {
FString Message;
if (Tokens.Num() == 0) {
Message = TEXT("Function prototype cannot be blank");
}
for (int i = 0; i < Tokens.Num(); i++) {
if (i == NextToken) {
Message.Append(TEXT(" ? "));
} else {
if ((i > 0) && (FChar::IsAlpha(Tokens[i][0])) && (FChar::IsAlpha(Tokens[i-1][0]))) {
Message.Append(TEXT(" "));
}
}
Message.Append(Tokens[i]);
}
Empty();
ErrorMessage = Message;
}
void FlxParsedProto::Parse(const FString &str) {
Empty();
// Step one: tokenize.
int offset = 0;
while (offset < str.Len()) {
TCHAR c = str[offset];
if (FChar::IsWhitespace(c)) {
offset++;
} else if (FChar::IsAlpha(c)) {
int lo = offset;
while ((offset < str.Len()) && FChar::IsAlnum(str[offset])) offset++;
Tokens.Add(str.Mid(lo, offset-lo));
} else if (str.Mid(offset, 3) == TEXT("...")) {
Tokens.Add(str.Mid(offset, 3));
offset += 3;
} else if (FChar::IsPunct(c)) {
Tokens.Add(str.Mid(offset, 1));
offset += 1;
} else {
Empty();
ErrorMessage = FString::Printf(TEXT("%s ? %s"), *str.Mid(0, offset), *str.Mid(offset));
return;
}
}
NextToken = 0;
// Step two: Parse.
if (!IsLiteral(TEXT("function"))) return Syntax();
NextToken++;
if (!IsLiteral(TEXT("*")) && !IsIdent()) return Syntax();
ClassName = Tokens[NextToken++];
if (!IsLiteral(TEXT("."))) return Syntax();
NextToken++;
if (!IsIdent()) return Syntax();
FunctionName = Tokens[NextToken++];
if (!IsLiteral(TEXT("("))) return Syntax();
NextToken++;
if (IsIdent()) {
while (true) {
if (!IsIdent()) return Syntax();
Arguments.Add(Tokens[NextToken++]);
if (!IsLiteral(TEXT(","))) break;
NextToken++;
}
}
if (!IsLiteral(TEXT(")"))) return Syntax();
NextToken++;
if (IsLiteral(TEXT(":"))) {
NextToken++;
while (true) {
if (IsLiteral(TEXT("..."))) {
ExtraReturnValues = true;
NextToken++;
break;
} else if (IsIdent()) {
ReturnValues.Add(Tokens[NextToken++]);
if (!IsLiteral(TEXT(","))) break;
NextToken++;
} else {
return Syntax();
}
}
}
if (NextToken != Tokens.Num()) return Syntax();
}
UK2Node_LuaCall::UK2Node_LuaCall(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
NodeTooltip = LOCTEXT("NodeTooltip",
"Probe or Invoke a Lua function.\n"
FString ArgTypes = UlxLuaCallLibrary::AllKnownArgumentTypes();
FString RetTypes = UlxLuaCallLibrary::AllKnownReturnValueTypes();
NodeTooltip = FText::FromString(FString::Printf(TEXT(
"Call a Lua function.\n"
"\n"
"The lua function prototype must be a hardwired string which must look like\n"
"one of the following:"
"one of the following:\n"
"\n"
" function cname.fname(arg1, arg2)"
" function cname.fname(arg1, arg2) : ret1, ret2\n"
" function cname.fname(arg1, arg2) : ret1, ret2, ...\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"
"\n"
"You must specify types for the argument and return value pins. The:\n"
"types that you may specify are:\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"
@@ -255,12 +138,7 @@ UK2Node_LuaCall::UK2Node_LuaCall(const FObjectInitializer& ObjectInitializer)
"\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"
"Argument and return value pins have wildcard types initially, you can\n"
"hook them to inputs and outputs of the following types:\n"
"\n"
" string, name, float, boolean, vector\n"
"\n");
"\n"), *ArgTypes, *RetTypes));
}
void UK2Node_LuaCall::AllocateDefaultPins()
@@ -273,7 +151,7 @@ void UK2Node_LuaCall::CreateCorrectPins()
{
if (LuaFunctionPrototype.IsEmpty())
{
LuaFunctionPrototype = TEXT("function class.func(arg1, arg2) : ret1, ret2");
LuaFunctionPrototype = TEXT("class.func(int arg1, int arg2) : int ret1, int ret2");
}
if (FindPin(UEdGraphSchema_K2::PN_Execute) == nullptr) {
@@ -325,28 +203,44 @@ void UK2Node_LuaCall::CreateCorrectPins()
}
// Create Argument pins in the correct order, reusing old pins where possible.
for (const FString& Name : ParsedProto.Arguments)
for (const FlxParsedProto::Pin & Pin : ParsedProto.Arguments)
{
FName PrefixedName = ArgumentNameAddPrefix(Name);
FName PrefixedName = ArgumentNameAddPrefix(Pin.Name);
UFunction *Accessor = UlxLuaCallLibrary::GetArgumentPacker(Pin.Type);
if (Accessor == nullptr) {
bHasCompilerMessage = true;
ErrorType = EMessageSeverity::Error;
ErrorMsg = FString::Printf(TEXT("Unknown argument type: %s"), *Pin.Type);
continue;
}
FEdGraphPinType PinType = GetPinType(Accessor->FindPropertyByName(TEXT("Value")));
UEdGraphPin **OldPin = OldArgumentPins.Find(PrefixedName);
if (OldPin != nullptr) {
if ((OldPin != nullptr) && ((*OldPin)->PinType == PinType)) {
Pins.Emplace(*OldPin);
OldArgumentPins.Remove(PrefixedName);
} else {
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, PrefixedName);
CreatePin(EGPD_Input, PinType, PrefixedName);
}
}
// Create ReturnValue pins in the correct order, reusing old pins where possible.
for (const FString& Name : ParsedProto.ReturnValues)
for (const FlxParsedProto::Pin & Pin : ParsedProto.ReturnValues)
{
FName PrefixedName = ReturnValueNameAddPrefix(Name);
FName PrefixedName = ReturnValueNameAddPrefix(Pin.Name);
UFunction *Accessor = UlxLuaCallLibrary::GetReturnValueUnpacker(Pin.Type);
if (Accessor == nullptr) {
bHasCompilerMessage = true;
ErrorType = EMessageSeverity::Error;
ErrorMsg = FString::Printf(TEXT("Unknown return value type: %s"), *Pin.Type);
continue;
}
FEdGraphPinType PinType = GetPinType(Accessor->GetReturnProperty());
UEdGraphPin **OldPin = OldReturnValuePins.Find(PrefixedName);
if (OldPin != nullptr) {
if ((OldPin != nullptr) && ((*OldPin)->PinType == PinType)) {
Pins.Emplace(*OldPin);
OldReturnValuePins.Remove(PrefixedName);
} else {
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Wildcard, PrefixedName);
CreatePin(EGPD_Output, PinType, PrefixedName);
}
}
@@ -366,53 +260,9 @@ void UK2Node_LuaCall::CreateCorrectPins()
}
void UK2Node_LuaCall::SynchronizePinType(UEdGraphPin* Pin)
{
if (IsArgumentPin(Pin) || IsReturnValuePin(Pin))
{
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(GetSchema());
bool bPinTypeChanged = false;
if (Pin->LinkedTo.Num() == 0)
{
static const FEdGraphPinType WildcardPinType = FEdGraphPinType(UEdGraphSchema_K2::PC_Wildcard, NAME_None, nullptr, EPinContainerType::None, false, FEdGraphTerminalType());
// Ensure wildcard
if (Pin->PinType != WildcardPinType)
{
Pin->PinType = WildcardPinType;
bPinTypeChanged = true;
}
}
else
{
UEdGraphPin* OtherPin = Pin->LinkedTo[0];
// Take the type of the connected pin
if (Pin->PinType != OtherPin->PinType)
{
Pin->PinType = OtherPin->PinType;
bPinTypeChanged = true;
}
}
if (bPinTypeChanged)
{
// Let the graph know to refresh
GetGraph()->NotifyNodeChanged(this);
UBlueprint* Blueprint = GetBlueprint();
if (!Blueprint->bBeingCompiled)
{
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
}
}
}
FText UK2Node_LuaCall::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
return LOCTEXT("LuaCall_Title", "Probe or Invoke a Lua Function");
return LOCTEXT("LuaCall_Title", "Call a Lua Function");
}
FText UK2Node_LuaCall::GetPinDisplayName(const UEdGraphPin* Pin) const
@@ -455,7 +305,6 @@ void UK2Node_LuaCall::PostEditChangeProperty(struct FPropertyChangedEvent& Prope
void UK2Node_LuaCall::PinConnectionListChanged(UEdGraphPin* Pin)
{
Modify();
SynchronizePinType(Pin);
}
void UK2Node_LuaCall::PinDefaultValueChanged(UEdGraphPin* Pin)
@@ -468,12 +317,6 @@ void UK2Node_LuaCall::PinDefaultValueChanged(UEdGraphPin* Pin)
}
}
void UK2Node_LuaCall::PinTypeChanged(UEdGraphPin* Pin)
{
SynchronizePinType(Pin);
Super::PinTypeChanged(Pin);
}
FText UK2Node_LuaCall::GetTooltipText() const
{
return NodeTooltip;
@@ -482,55 +325,11 @@ FText UK2Node_LuaCall::GetTooltipText() const
void UK2Node_LuaCall::PostReconstructNode()
{
Super::PostReconstructNode();
UEdGraph* OuterGraph = GetGraph();
if (!IsTemplate() && OuterGraph && OuterGraph->Schema) {
for (UEdGraphPin* CurrentPin : Pins)
{
SynchronizePinType(CurrentPin);
}
}
CreateCorrectPins();
}
#define LuaCallLibraryFunction(name) (UlxLuaCallLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxLuaCallLibrary, name)))
static UFunction *GetArgumentPackingFunction(const FEdGraphPinType &Type)
{
if (Type.PinCategory == UEdGraphSchema_K2::PC_Real)
{
return LuaCallLibraryFunction(LuaCallAddFloatParameter);
}
if (Type.PinCategory == UEdGraphSchema_K2::PC_Int)
{
return LuaCallLibraryFunction(LuaCallAddIntParameter);
}
else if (Type.PinCategory == UEdGraphSchema_K2::PC_Boolean)
{
return LuaCallLibraryFunction(LuaCallAddBooleanParameter);
}
else if (Type.PinCategory == UEdGraphSchema_K2::PC_Name)
{
return LuaCallLibraryFunction(LuaCallAddNameParameter);
}
else if (Type.PinCategory == UEdGraphSchema_K2::PC_String)
{
return LuaCallLibraryFunction(LuaCallAddStringParameter);
}
else if ((Type.PinCategory == UEdGraphSchema_K2::PC_Struct) && (Type.PinSubCategoryObject == TBaseStructure<FVector>::Get()))
{
return LuaCallLibraryFunction(LuaCallAddVectorParameter);
}
else if ((Type.PinCategory == UEdGraphSchema_K2::PC_Struct) && (Type.PinSubCategoryObject == TBaseStructure<FVector2D>::Get()))
{
return LuaCallLibraryFunction(LuaCallAddVector2DParameter);
}
else
{
return nullptr;
}
}
void UK2Node_LuaCall::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
@@ -555,38 +354,56 @@ void UK2Node_LuaCall::ExpandNode(class FKismetCompilerContext& CompilerContext,
UK2Node_CallFunction* PrevNode = BeginNode;
// Add Packing operations for all argument pins.
for (UEdGraphPin* Pin : Pins)
for (const FlxParsedProto::Pin &PinInfo : ParsedProto.Arguments)
{
if (IsArgumentPin(Pin))
UEdGraphPin *Pin = FindPinChecked(ArgumentNameAddPrefix(PinInfo.Name));
UFunction *PackingFunc = UlxLuaCallLibrary::GetArgumentPacker(PinInfo.Type);
if (PackingFunc == nullptr)
{
UFunction *PackingFunc = GetArgumentPackingFunction(Pin->PinType);
if (PackingFunc != nullptr)
{
UK2Node_CallFunction *PackNode = MakeCallFunctionNode(PackingFunc);
CompilerContext.MovePinLinksToIntermediate(*Pin, *PackNode->FindPinChecked(TEXT("Value")));
PrevNode->GetThenPin()->MakeLinkTo(PackNode->GetExecPin());
PrevNode = PackNode;
}
else
{
FText PinName = GetPinDisplayName(Pin);
FText PinType = FText::FromName(Pin->PinType.PinCategory);
FText Error = FText::Format(LOCTEXT("Error_UnexpectedPinType", "Pin '{0}' has an unexpected type: {1}"), PinName, PinType);
CompilerContext.MessageLog.Error(*Error.ToString());
}
// 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(PackingFunc);
CompilerContext.MovePinLinksToIntermediate(*Pin, *PackNode->FindPinChecked(TEXT("Value")));
PrevNode->GetThenPin()->MakeLinkTo(PackNode->GetExecPin());
PrevNode = PackNode;
}
// Add the invoke or probe node.
bool IsInvoke = (FindPin(InvokeOrProbePinName, EGPD_Input)->DefaultValue == TEXT("Invoke"));
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;
// Add Unpacking operations for all argument pins.
for (const FlxParsedProto::Pin &PinInfo : ParsedProto.ReturnValues)
{
UEdGraphPin *Pin = FindPinChecked(ReturnValueNameAddPrefix(PinInfo.Name));
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(UnpackingFunc);
CompilerContext.MovePinLinksToIntermediate(*Pin, *UnpackNode->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue));
PrevNode->GetThenPin()->MakeLinkTo(UnpackNode->GetExecPin());
PrevNode = UnpackNode;
}
// Make sure we didn't have return values for an invoke.
if (IsInvoke && (ParsedProto.ReturnValues.Num() > 0))
{
CompilerContext.MessageLog.Error(TEXT("Lua Call in Invoke mode does not support return values"));
}
// Link up the Exec pins.
CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *BeginNode->GetExecPin());
CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *ActionNode->GetThenPin());
CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *PrevNode->GetThenPin());
BreakAllNodeLinks();
}
@@ -653,21 +470,9 @@ bool UK2Node_LuaCall::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEd
return true;
}
// Argument input pins may only be connected to packable types.
if (IsArgumentPin(MyPin))
{
UFunction *Packer = GetArgumentPackingFunction(OtherPin->PinType);
if (Packer == nullptr)
{
OutReason = LOCTEXT("Error_InvalidArgumentType", "Lua Call Arguments may be float, boolean, string, name, or vector.").ToString();
return true;
}
}
return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason);
}
void UK2Node_LuaCall::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
{
// actions get registered under specific object-keys; the idea is that