From e982fec1a4eb4c3214b4651900a7e22216226e7e Mon Sep 17 00:00:00 2001 From: jyelon Date: Mon, 30 Mar 2026 20:10:23 -0400 Subject: [PATCH] Code is all tore up --- Content/Testing/BP_Test.uasset | 4 +- .../UEWingman/Handlers/ActorComponent_Add.h | 7 +- .../Handlers/BlueprintVariable_Create.h | 59 ++- .../Handlers/BlueprintVariable_Dump.h | 18 +- .../Handlers/BlueprintVariable_Modify.h | 39 +- .../UEWingman/Handlers/GraphVariables_Dump.h | 59 +++ .../Handlers/GraphVariables_Modify.h | 50 ++ .../UEWingman/Handlers/Test_ParseVariables.h | 36 ++ .../Source/UEWingman/Handlers/Widget_Create.h | 8 +- .../UEWingman/Private/WingActorComponent.cpp | 8 + .../UEWingman/Private/WingGraphExport.cpp | 31 +- .../UEWingman/Private/WingTokenizer.cpp | 61 ++- .../Source/UEWingman/Private/WingTypes.cpp | 40 +- .../UEWingman/Private/WingVariables.cpp | 452 ++++++++++++++++-- .../Source/UEWingman/Public/WingTokenizer.h | 35 +- .../Source/UEWingman/Public/WingTypes.h | 6 +- .../Source/UEWingman/Public/WingUtils.h | 18 + .../Source/UEWingman/Public/WingVariables.h | 81 +++- Plugins/UEWingman/ue-wingman.py | 43 +- 19 files changed, 876 insertions(+), 179 deletions(-) create mode 100644 Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Dump.h create mode 100644 Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Modify.h create mode 100644 Plugins/UEWingman/Source/UEWingman/Handlers/Test_ParseVariables.h diff --git a/Content/Testing/BP_Test.uasset b/Content/Testing/BP_Test.uasset index f5e81a75..9262c558 100644 --- a/Content/Testing/BP_Test.uasset +++ b/Content/Testing/BP_Test.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24ab1c9e1c3fadda0d7e0f780d363808cc61e17ad912d212080714dd0811bbee -size 44331 +oid sha256:70b5ccb833a544d0c583defa35ed7e58d568e7a02f7fe5e37c9bc958b6f3eb56 +size 44409 diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Add.h b/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Add.h index 6de77dd2..b7340b45 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Add.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Add.h @@ -11,6 +11,7 @@ #include "Engine/SimpleConstructionScript.h" #include "Engine/SCS_Node.h" #include "Components/ActorComponent.h" +#include "Kismet2/BlueprintEditorUtils.h" #include "ActorComponent_Add.generated.h" @@ -51,8 +52,9 @@ public: // Check that the proposed name is valid FName InternalID = WingUtils::CheckProposedName(Component); if (InternalID.IsNone()) return; - TArray AllComponents = UWingComponentReference::GetAll(BP); - if (!WingUtils::FindNoneWithInternalID(InternalID, AllComponents, TEXT("Component"))) return; + TSet Names; + FBlueprintEditorUtils::GetClassVariableList(BP, Names); + if (!WingUtils::FindNoDuplicateName(Names, InternalID, TEXT("variable or component"))) return; // Resolve the component class by name UWingTypes::Requirements Req; @@ -65,6 +67,7 @@ public: if (!UWingComponentReference::CheckValidComponentClass(ComponentClass)) return; // Find the specified parent component + TArray AllComponents = UWingComponentReference::GetAll(BP); UWingComponentReference* ParentComp = WingUtils::FindOneWithExternalID(Parent, AllComponents, TEXT("Component")); if (!ParentComp) return; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Create.h index 3af3d0af..6d0ccf84 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Create.h @@ -4,13 +4,12 @@ #include "WingServer.h" #include "WingHandler.h" #include "WingFetcher.h" -#include "WingProperty.h" -#include "WingBlueprintVar.h" #include "WingUtils.h" #include "WingTypes.h" +#include "WingVariables.h" #include "Engine/Blueprint.h" -#include "EdGraphSchema_K2.h" #include "Kismet2/BlueprintEditorUtils.h" +#include "Kismet2/KismetEditorUtilities.h" #include "BlueprintVariable_Create.generated.h" @@ -27,15 +26,12 @@ public: UPROPERTY(meta=(Description="Blueprint name or package path")) FString Blueprint; - UPROPERTY(meta=(Description="Name of the new variable")) - FString Name; - - UPROPERTY(meta=(Optional, Description="Variable configuration: VarType, Category, DefaultValue, InstanceEditable, BlueprintReadOnly, ExposeOnSpawn, Private, ExposeToCinematics, etc.")) - FWingJsonObject Config; + UPROPERTY(meta=(Description="Variable declarations, one per line. Format: type name (flags) = default")) + FString Variables; virtual FString GetDescription() const override { - return TEXT("Add a new member variable to a Blueprint. Pass Config to set type, category, flags, etc."); + return TEXT("Add new member variables to a Blueprint. Format: 'type name (flags) = default', one per line."); } virtual void Handle() override @@ -44,32 +40,33 @@ public: UBlueprint* BP = F.Walk(Blueprint).Cast(); if (!BP) return; - // Check validity of the proposed name - FName InternalID = WingUtils::CheckProposedName(Name); - if (InternalID.IsNone()) return; - if (!WingUtils::FindNoneWithInternalID(InternalID, BP->NewVariables, TEXT("Variable"))) return; + // Parse the variable declarations. + WingVariables Parsed; + if (!WingVariables::ParseString(Variables, Parsed)) return; + if (Parsed.IsEmpty()) { UWingServer::Print(TEXT("ERROR: No variables specified.\n")); return; } + if (!Parsed.CheckSanity(WingVariables::Cat::Blueprint)) return; - // Add the variable with a default type - FEdGraphPinType DefaultType; - DefaultType.PinCategory = UEdGraphSchema_K2::PC_Int; - if (!FBlueprintEditorUtils::AddMemberVariable(BP, InternalID, DefaultType)) + // Check for name collisions against existing variables, components, and the like. + TSet Names; + FBlueprintEditorUtils::GetClassVariableList(BP, Names); + if (!WingUtils::FindNoDuplicateNames(Names, Parsed.GetVariables(), TEXT("variable or component"))) return; + + // Create the variables. + for (const WingVariables::Var& V : Parsed.GetVariables()) { - UWingServer::Printf(TEXT("ERROR: Failed to add variable '%s' to %s\n"), *Name, *WingUtils::FormatName(BP)); - return; - } - - // Find the newly created variable description - FWingBlueprintVar Editor(BP, Name); - if (Editor.NotFound()) return; - - // Apply config if provided - if (Config.Json && Config.Json->Values.Num() > 0) - { - if (!Editor.ApplyJson(Config.Json.Get())) + if (!FBlueprintEditorUtils::AddMemberVariable(BP, V.Name, V.Type)) + { + UWingServer::Printf(TEXT("ERROR: Failed to add variable '%s'\n"), + *WingUtils::ExternalizeID(V.Name)); return; + } } - UWingServer::Printf(TEXT("Created variable %s (%s) in %s\n"), - *Name, *UWingTypes::TypeToText(Editor.Desc->VarType), *WingUtils::FormatName(BP)); + // Update everything. + if (!Parsed.AssociateBlueprintNewVariables(BP)) return; + Parsed.UpdateVariableFlags(); + FKismetEditorUtilities::CompileBlueprint(BP); + if (!Parsed.UpdateVariableDefaults()) return; + UWingServer::Printf(TEXT("Success.\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Dump.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Dump.h index 8556c153..f80ef6b6 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Dump.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Dump.h @@ -4,10 +4,7 @@ #include "WingServer.h" #include "WingHandler.h" #include "WingFetcher.h" -#include "WingUtils.h" -#include "WingBlueprintVar.h" -#include "Engine/Blueprint.h" -#include "Kismet2/BlueprintEditorUtils.h" +#include "WingVariables.h" #include "BlueprintVariable_Dump.generated.h" @@ -24,12 +21,9 @@ public: UPROPERTY(meta=(Description="Blueprint name or package path")) FString Blueprint; - UPROPERTY(meta=(Description="Name of the variable to inspect")) - FString Variable; - virtual FString GetDescription() const override { - return TEXT("Show all editable properties of a Blueprint variable."); + return TEXT("List all member variables of a Blueprint."); } virtual void Handle() override @@ -38,10 +32,8 @@ public: UBlueprint* BP = F.Walk(Blueprint).Cast(); if (!BP) return; - FWingBlueprintVar Editor(BP, Variable); - if (Editor.NotFound()) return; - - UWingServer::Printf(TEXT("Variable %s in %s:\n"), *Variable, *WingUtils::FormatName(BP)); - Editor.Dump(); + WingVariables Vars = WingVariables::ParseBlueprintVariables(BP); + if (Vars.IsEmpty()) { UWingServer::Print(TEXT("No variables.\n")); return; } + Vars.PrintAll(); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Modify.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Modify.h index 7773f266..d959c15f 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Modify.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Modify.h @@ -4,12 +4,10 @@ #include "WingServer.h" #include "WingHandler.h" #include "WingFetcher.h" -#include "WingProperty.h" -#include "WingBlueprintVar.h" #include "WingUtils.h" -#include "WingTypes.h" +#include "WingVariables.h" #include "Engine/Blueprint.h" -#include "Kismet2/BlueprintEditorUtils.h" +#include "Kismet2/KismetEditorUtilities.h" #include "BlueprintVariable_Modify.generated.h" @@ -26,15 +24,12 @@ public: UPROPERTY(meta=(Description="Blueprint name or package path")) FString Blueprint; - UPROPERTY(meta=(Description="Name of the variable to modify")) - FString Variable; - - UPROPERTY(meta=(Description="Properties to change: VarType, Category, DefaultValue, InstanceEditable, BlueprintReadOnly, ExposeOnSpawn, Private, ExposeToCinematics, etc.")) - FWingJsonObject Properties; + UPROPERTY(meta=(Description="Variable declarations, one per line. Format: type name (flags) = default")) + FString Variables; virtual FString GetDescription() const override { - return TEXT("Modify properties of an existing Blueprint variable."); + return TEXT("Modify existing Blueprint variables. Format: 'type name (flags) = default', one per line."); } virtual void Handle() override @@ -43,19 +38,21 @@ public: UBlueprint* BP = F.Walk(Blueprint).Cast(); if (!BP) return; - FWingBlueprintVar Editor(BP, Variable); - if (Editor.NotFound()) return; + // Parse the variable declarations. + WingVariables Parsed; + if (!WingVariables::ParseString(Variables, Parsed)) return; + if (Parsed.IsEmpty()) { UWingServer::Print(TEXT("ERROR: No variables specified.\n")); return; } + if (!Parsed.CheckSanity(WingVariables::Cat::Blueprint)) return; - if (!Properties.Json || Properties.Json->Values.Num() == 0) - { - UWingServer::Print(TEXT("ERROR: No properties specified\n")); - return; - } + // Associate with existing blueprint variables. + if (!Parsed.AssociateBlueprintNewVariables(BP)) return; - if (!Editor.ApplyJson(Properties.Json.Get())) - return; + // Update types and flags, compile, then update defaults. + Parsed.UpdateVariableTypes(); + Parsed.UpdateVariableFlags(); + FKismetEditorUtilities::CompileBlueprint(BP); + if (!Parsed.UpdateVariableDefaults()) return; - UWingServer::Printf(TEXT("Modified variable %s (%s) in %s\n"), - *Variable, *UWingTypes::TypeToText(Editor.Desc->VarType), *WingUtils::FormatName(BP)); + UWingServer::Printf(TEXT("Success.\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Dump.h b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Dump.h new file mode 100644 index 00000000..191b6a84 --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Dump.h @@ -0,0 +1,59 @@ +#pragma once + +#include "CoreMinimal.h" +#include "WingHandler.h" +#include "WingServer.h" +#include "WingFetcher.h" +#include "WingVariables.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "GraphVariables_Dump.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UWing_GraphVariables_Dump : public UObject, public IWingHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Path to a function graph (e.g. '/Game/MyBP,graph:MyFunction')")) + FString Graph; + + virtual FString GetDescription() const override + { + return TEXT("List all arguments, return values, and local variables of a function graph."); + } + + virtual void Handle() override + { + WingFetcher F; + UEdGraph* G = F.Walk(Graph).Cast(); + if (!G) return; + + FStringBuilderBase &Output = UWingServer::GetPrintBuffer(); + TWeakObjectPtr EntryNode; + TWeakObjectPtr ResultNode; + FBlueprintEditorUtils::GetEntryAndResultNodes(G, EntryNode, ResultNode); + WingVariables Arguments = WingVariables::ParseEditablePinBase(EntryNode.Get()); + WingVariables ReturnValues = WingVariables::ParseEditablePinBase(EntryNode.Get()); + WingVariables Locals = WingVariables::ParseFunctionLocalVariables(EntryNode.Get()); + if (!Arguments.GetVariables().IsEmpty()) + { + Output.Appendf(TEXT("Arguments:\n")); + Arguments.PrintAll(Output, 4); + } + if (!ReturnValues.GetVariables().IsEmpty()) + { + Output.Appendf(TEXT("ReturnValues:\n")); + ReturnValues.PrintAll(Output, 4); + } + if (!Locals.GetVariables().IsEmpty()) + { + Output.Appendf(TEXT("LocalVariables:\n")); + Locals.PrintAll(Output, 4); + } + } +}; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Modify.h b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Modify.h new file mode 100644 index 00000000..918aef2c --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Modify.h @@ -0,0 +1,50 @@ +#pragma once + +#include "CoreMinimal.h" +#include "WingHandler.h" +#include "WingServer.h" +#include "WingFetcher.h" +#include "WingVariables.h" +#include "GraphVariables_Modify.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UWing_GraphVariables_Modify : public UObject, public IWingHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Path to a function graph (e.g. '/Game/MyBP,graph:MyFunction')")) + FString Graph; + + UPROPERTY(meta=(Description="Variable declarations, one per line. Format: type name = default")) + FString Variables; + + virtual FString GetDescription() const override + { + return TEXT("Modify existing local variables of a function graph."); + } + + virtual void Handle() override + { + WingFetcher F; + UEdGraph* G = F.Walk(Graph).Cast(); + if (!G) return; + + WingVariables Parsed; + if (!WingVariables::ParseString(Variables, Parsed)) return; + if (Parsed.IsEmpty()) { UWingServer::Print(TEXT("ERROR: No variables specified.\n")); return; } + if (!Parsed.CheckSanity(WingVariables::Cat::FunctionLocal)) return; + + if (!Parsed.AssociateFunctionLocalVariables(G)) return; + Parsed.UpdateVariableTypes(); + Parsed.UpdateVariableFlags(); + Parsed.UpdateVariableDefaults(); + + UWingServer::Printf(TEXT("Success.\n")); + } +}; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Test_ParseVariables.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Test_ParseVariables.h new file mode 100644 index 00000000..31c82bee --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Test_ParseVariables.h @@ -0,0 +1,36 @@ +#pragma once + +#include "CoreMinimal.h" +#include "WingHandler.h" +#include "WingServer.h" +#include "WingVariables.h" +#include "Test_ParseVariables.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UWing_Test_ParseVariables : public UObject, public IWingHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Variable declarations, one per line. Format: type name (flags) = default")) + FString Variables; + + virtual FString GetDescription() const override + { + return TEXT("Test: parse variable declarations and print them back out."); + } + + virtual void Handle() override + { + WingVariables Parsed; + if (!WingVariables::ParseString(Variables, Parsed)) return; + + UWingServer::Print(TEXT("Parsed variables:\n")); + Parsed.PrintAll(1); + } +}; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Create.h index 0559bf13..72e77732 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Create.h @@ -9,6 +9,7 @@ #include "WidgetBlueprint.h" #include "Blueprint/WidgetTree.h" #include "Blueprint/UserWidget.h" +#include "Kismet2/BlueprintEditorUtils.h" #include "Components/PanelWidget.h" #include "Widget_Create.generated.h" @@ -72,8 +73,11 @@ public: // Check that the name is unique among existing widgets. TArray AllWidgets; Tree->GetAllWidgets(AllWidgets); - if (!WingUtils::FindNoneWithInternalID(InternalID, AllWidgets, TEXT("Widget"))) return; - + TSet Names; + FBlueprintEditorUtils::GetClassVariableList(BP, Names); + if (!WingUtils::FindNoDuplicateNames(Names, AllWidgets, TEXT("widget or variable"))) return; + if (!WingUtils::FindNoDuplicateName(Names, InternalID, TEXT("widget or variable"))) return; + // If a parent is specified, find it and verify it's a panel. UPanelWidget* ParentPanel = nullptr; if (!Parent.IsEmpty()) diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingActorComponent.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingActorComponent.cpp index 877edc73..67370aaf 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingActorComponent.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingActorComponent.cpp @@ -141,6 +141,14 @@ void UWingComponentReference::AddChildNode(UBlueprint *BP, USCS_Node *NewNode, F bool UWingComponentReference::AddComponent(UBlueprint *BP, UClass *Class, UWingComponentReference *Parent, FName Name) { + TSet Names; + FBlueprintEditorUtils::GetClassVariableList(BP, Names); + if (Names.Contains(Name)) + { + UWingServer::Printf(TEXT("There is already a variable or component named %s in %s.\n"), + *WingUtils::ExternalizeID(Name), *WingUtils::FormatName(Class)); + return false; + } FoundComponent ExistingComponent = FindComponent(BP, Name); if (!CheckNoSuchComponent(ExistingComponent)) return false; FoundComponent ParentComponent = FindComponent(BP, Parent->VariableName); diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp index 292c5ef6..9ac80a27 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp @@ -13,6 +13,7 @@ #include "WingFunctionArgs.h" #include "WingVariables.h" #include "MaterialGraph/MaterialGraphNode.h" +#include "Kismet2/BlueprintEditorUtils.h" WingGraphExport::WingGraphExport(UEdGraph* InGraph) : Graph(InGraph) @@ -288,18 +289,26 @@ void WingGraphExport::EmitMaterialProperties(UEdGraphNode* Node, FStringBuilderB void WingGraphExport::EmitLocalVariables() { - for (UEdGraphNode* Node : Graph->Nodes) + TWeakObjectPtr EntryNode; + TWeakObjectPtr ResultNode; + FBlueprintEditorUtils::GetEntryAndResultNodes(Graph, EntryNode, ResultNode); + WingVariables Arguments = WingVariables::ParseEditablePinBase(EntryNode.Get()); + WingVariables ReturnValues = WingVariables::ParseEditablePinBase(EntryNode.Get()); + WingVariables Locals = WingVariables::ParseFunctionLocalVariables(EntryNode.Get()); + if (!Arguments.GetVariables().IsEmpty()) { - UK2Node_FunctionEntry* EntryNode = Cast(Node); - if (!EntryNode) continue; - - WingVariables Locals = WingVariables::ParseFunctionLocalVariables(EntryNode); - if (!Locals.IsEmpty()) - { - Output.Appendf(TEXT("Function Local Variables:\n")); - Locals.PrintAll(Output, 4); - } - break; + Output.Appendf(TEXT("Arguments:\n")); + Arguments.PrintAll(Output, 4); + } + if (!ReturnValues.GetVariables().IsEmpty()) + { + Output.Appendf(TEXT("ReturnValues:\n")); + ReturnValues.PrintAll(Output, 4); + } + if (!Locals.GetVariables().IsEmpty()) + { + Output.Appendf(TEXT("LocalVariables:\n")); + Locals.PrintAll(Output, 4); } } diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingTokenizer.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingTokenizer.cpp index bea6773e..1b09bfb0 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingTokenizer.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingTokenizer.cpp @@ -44,19 +44,21 @@ WingCharacterClasses::WingCharacterClasses() WingCharacterClasses WingCharacterClasses::TheSet; -void WingTokenizer::Add(TCHAR Type, FName Name) +void WingTokenizer::Add(TCHAR Type, FName Name, FStringView Before, FStringView After) { Token T; T.Type = Type; T.Name = Name; + T.Source = FStringView(Before.GetData(), After.GetData() - Before.GetData()); Tokens.Add(T); } -void WingTokenizer::Add(TCHAR Type, FStringView Rest) +void WingTokenizer::Add(TCHAR Type, FStringView Rest, FStringView Before, FStringView After) { Token T; T.Type = Type; T.Rest = Rest; + T.Source = FStringView(Before.GetData(), After.GetData() - Before.GetData()); Tokens.Add(T); } @@ -125,6 +127,16 @@ TCHAR WingTokenizer::TokenizeEscapeSequence(FStringView &Rest, FString &Error) return Result; } +FStringView WingTokenizer::TokenizeAssetName(FStringView &Rest, FString &Error) +{ + if (!Error.IsEmpty()) return FStringView(); + int i = 0; + while ((i < Rest.Len()) && (Rest[i] != ',')) i++; + FStringView Result = Rest.SubStr(0, i); + Rest = Rest.RightChop(i); + return Result; +} + FName WingTokenizer::TokenizeIdentifier(FStringView &Rest, FString &Error) { if (!Error.IsEmpty()) return FName(); @@ -185,21 +197,31 @@ WingTokenizer::WingTokenizer(const FString& In) Rest = Rest.RightChop(1); continue; } + FStringView Before = Rest; if (Ch == '=') { - Add(RestOfLine, Rest.RightChop(1)); + FStringView Body = Rest.RightChop(1); + Rest = Rest.RightChop(Rest.Len()); + Add(RestOfLine, Body, Before, Rest); break; } + if (Ch == '/') + { + FStringView Asset = TokenizeAssetName(Rest, Error); + Add(AssetName, Asset, Before, Rest); + continue; + } if ((Ch == '.') || (Ch == '&')) { - Add(Identifier, TokenizeIdentifier(Rest, Error)); + FName Id = TokenizeIdentifier(Rest, Error); + Add(Identifier, Id, Before, Rest); continue; } Cat Category = WingCharacterClasses::GetCat(Ch); if (Category == Cat::Punctuation) { - Add(Ch, FString()); Rest = Rest.RightChop(1); + Add(Ch, FString(), Before, Rest); continue; } if (Category == Cat::Control) @@ -207,17 +229,38 @@ WingTokenizer::WingTokenizer(const FString& In) Error = "Control characters in input, not allowed"; break; } - Add(Identifier, TokenizeIdentifier(Rest, Error)); + FName Id = TokenizeIdentifier(Rest, Error); + Add(Identifier, Id, Before, Rest); continue; } if (!Error.IsEmpty()) Tokens.Empty(); // Two sentinels means we can safely do lookahead 2 without risk. - Add(0, FName()); - Add(0, FName()); + Rest = Rest.LeftChop(Rest.Len()); + Add(0, FName(), Rest, Rest); + Add(0, FName(), Rest, Rest); Next = Tokens.GetData(); } +void WingTokenizer::SaveCursor(FName Name) +{ + int Cursor = Next - Tokens.GetData(); + for (auto &Pair : SavedCursor) if (Pair.Key == Name) { Pair.Value = Cursor; return; } + SavedCursor.Emplace(Name, Cursor); +} + +FStringView WingTokenizer::GetRange(FName SavePos, int Extra) const +{ + int Lo = 0; + for (auto &Pair : SavedCursor) if (Pair.Key == SavePos) Lo = Pair.Value; + int Hi = (Next - Tokens.GetData()) + Extra; + Hi = FMath::Clamp(Hi, Lo, Tokens.Num()); + if (Lo >= Hi) return FStringView(); + const TCHAR* Start = Tokens[Lo].Source.GetData(); + const TCHAR* End = Tokens[Hi - 1].Source.GetData() + Tokens[Hi - 1].Source.Len(); + return FStringView(Start, End - Start); +} + void WingTokenizer::PrintEverything(FStringBuilderBase &Out) const { if (!Error.IsEmpty()) @@ -242,7 +285,7 @@ void WingTokenizer::PrintEverything(FStringBuilderBase &Out) const } } } - if (T.Type == RestOfLine) + if ((T.Type == RestOfLine) || (T.Type == AssetName)) { Out.Appendf(TEXT("[%s]"), *FString(T.Rest)); } diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp index 1a71ec06..fdd02428 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp @@ -431,21 +431,16 @@ static const FName NAME_TypeSoft(TEXT("Soft")); static const FName NAME_TypeClass(TEXT("Class")); static const FName NAME_TypeSoftClass(TEXT("SoftClass")); +static const FName NAME_StartOfType("Start-of-Type"); + void UWingTypes::PrintParseError(WingTokenizer& Tok, const TCHAR* Message) { - UWingServer::Printf(TEXT("ERROR parsing type '%s' — %s\n"), *Tok.GetInput(), Message); + FString TypeText(Tok.GetRange(NAME_StartOfType, 1)); + UWingServer::Printf(TEXT("ERROR parsing type '%s' — %s\n"), *TypeText, Message); UWingServer::SuggestManual(WingManual::Section::Types); } -bool UWingTypes::ParseEOF(WingTokenizer& Tok) -{ - if (Tok.NextType() != 0) - { - PrintParseError(Tok, TEXT("extra tokens at end of input")); - return false; - } - return true; -} + bool UWingTypes::ParseChar(WingTokenizer& Tok, TCHAR c) { @@ -547,7 +542,6 @@ bool UWingTypes::ParseType(WingTokenizer& Tok, FEdGraphPinType& OutType) { if (!ParseMaybeWrapped(Tok, OutType)) return false; } - if (!ParseEOF(Tok)) return false; return true; } @@ -611,8 +605,12 @@ bool UWingTypes::ResolveShortName(WingTokenizer& Tok, const FString &Name, FEdGr // --------------------------------------------------------------------------- -bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, const Requirements &Require) +bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, const Requirements &Require, bool CheckEOF) { + UWingTypes* Types = GEditor->GetEditorSubsystem(); + check(Types); + Tok.SaveCursor(NAME_StartOfType); + if (!Require.BlueprintType.IsSet() || !Require.Blueprintable.IsSet() || !Require.AllowContainer.IsSet()) @@ -621,19 +619,23 @@ bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, co return false; } - UWingTypes* Types = GEditor->GetEditorSubsystem(); - check(Types); - WingTokenizer Tok(Text); OutPinType = FEdGraphPinType(); if (!Types->ParseType(Tok, OutPinType)) { OutPinType = FEdGraphPinType(); return false; } + if (CheckEOF && (Tok.NextType() != 0)) + { + PrintParseError(Tok, TEXT("Extra tokens at end of Type name")); + OutPinType = FEdGraphPinType(); return false; + } + if (!Require.AllowContainer.GetValue()) { if (OutPinType.IsContainer()) { + FString Text(Tok.GetRange(NAME_StartOfType, 0)); UWingServer::Printf(TEXT("ERROR: Type '%s' is a container, not allowed here\n"), *Text); UWingServer::SuggestManual(WingManual::Section::HandlerHelp); OutPinType = FEdGraphPinType(); return false; @@ -665,6 +667,7 @@ bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, co { if (!IsBlueprintType(OutPinType)) { + FString Text(Tok.GetRange(NAME_StartOfType, 0)); UWingServer::Printf(TEXT("ERROR: Not a blueprint type: %s\n"), *Text); UWingServer::SuggestManual(WingManual::Section::HandlerHelp); OutPinType = FEdGraphPinType(); return false; @@ -675,6 +678,7 @@ bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, co { if (!IsBlueprintable(OutPinType)) { + FString Text(Tok.GetRange(NAME_StartOfType, 0)); UWingServer::Printf(TEXT("ERROR: Not a blueprintable type: %s\n"), *Text); UWingServer::SuggestManual(WingManual::Section::HandlerHelp); OutPinType = FEdGraphPinType(); return false; @@ -684,6 +688,12 @@ bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, co return true; } +bool UWingTypes::TextToType(const FString &Text, FEdGraphPinType& OutPinType, const Requirements &Require) +{ + WingTokenizer Tok(Text); + return TextToType(Tok, OutPinType, Require, true); +} + UClass* UWingTypes::TextToOneObjectType(const FString& Text, const Requirements &Require) { FEdGraphPinType PinType; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingVariables.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingVariables.cpp index a5c568ea..11248552 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingVariables.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingVariables.cpp @@ -2,8 +2,13 @@ #include "WingServer.h" #include "WingTypes.h" #include "WingUtils.h" +#include "WingProperty.h" +#include "WingTokenizer.h" #include "EdGraphSchema_K2.h" #include "K2Node_FunctionEntry.h" +#include "K2Node_FunctionResult.h" +#include "K2Node_Tunnel.h" +#include "K2Node_EditablePinBase.h" #include "Kismet2/BlueprintEditorUtils.h" // Flag names used for blueprint variables. @@ -33,30 +38,167 @@ void WingVariables::Var::AddFlagIfRelevant(FName Flag, WingVariables::Cat C) if (GetRelevantFlagSet(C).Contains(Flag)) Flags.Add(Flag); } -WingVariables::Var WingVariables::ParseVariableDescription(const FBPVariableDescription &Desc, WingVariables::Cat Category) +WingVariables WingVariables::ParseBlueprintVariables(UBlueprint *BP) { - Var Result; - Result.Name = Desc.VarName; - Result.Type = Desc.VarType; - - if (!(Desc.PropertyFlags & CPF_DisableEditOnInstance)) - Result.AddFlagIfRelevant(Flag_InstanceEditable, Category); - - if (Desc.PropertyFlags & CPF_BlueprintReadOnly) - Result.AddFlagIfRelevant(Flag_BlueprintReadOnly, Category); + WingVariables Result; + Result.Category = Cat::Blueprint; + Result.Blueprint = BP; + for (FBPVariableDescription& Desc : BP->NewVariables) + { + // Skip event dispatchers. + if (Desc.VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate) continue; - if (Desc.PropertyFlags & CPF_Interp) - Result.AddFlagIfRelevant(Flag_ExposeToCinematics, Category); + // Parse the bulk of the flags. + Var V = ParseVariableDescription(Desc, Cat::Blueprint); + V.BPVar = &Desc; - if (Desc.HasMetaData(FBlueprintMetadata::MD_ExposeOnSpawn)) - Result.AddFlagIfRelevant(Flag_ExposeOnSpawn, Category); - - if (Desc.HasMetaData(FBlueprintMetadata::MD_Private)) - Result.AddFlagIfRelevant(Flag_Private, Category); + // Read default value from CDO if available. + if (BP->GeneratedClass) + { + UObject* CDO = BP->GeneratedClass->GetDefaultObject(); + FProperty* Prop = BP->GeneratedClass->FindPropertyByName(Desc.VarName); + if (CDO && Prop) + V.DefaultValue = FWingProperty(Prop, CDO).GetText(); + } + Result.Variables.Add(MoveTemp(V)); + } return Result; } +WingVariables WingVariables::ParseFunctionLocalVariables(UK2Node_EditablePinBase *Node) +{ + WingVariables Result; + if (UK2Node_FunctionEntry *Func = Cast(Node)) + { + Result.Category = Cat::FunctionLocal; + Result.PinBase = Node; + for (FBPVariableDescription& Desc : Func->LocalVariables) + { + Var V = ParseVariableDescription(Desc, Cat::FunctionLocal); + V.DefaultValue = Desc.DefaultValue; + V.BPVar = &Desc; + Result.Variables.Add(MoveTemp(V)); + } + } + else + { + Result.Category = Cat::Empty; + } + return Result; +} + +WingVariables WingVariables::ParseEditablePinBase(UK2Node_EditablePinBase* Node) +{ + WingVariables Result; + if (Node != nullptr) + { + Result.Category = Cat::EditablePinBase; + Result.PinBase = Node; + for (const TSharedPtr& PinInfo : Node->UserDefinedPins) + { + Var V; + V.Name = PinInfo->PinName; + V.Type = PinInfo->PinType; + V.DefaultValue = PinInfo->PinDefaultValue; + V.Pin = PinInfo; + Result.Variables.Add(MoveTemp(V)); + } + } + else + { + Result.Category = Cat::Empty; + } + return Result; +} + +bool WingVariables::AssociateBlueprintNewVariables(UBlueprint *BP) +{ + Category = Cat::Blueprint; + Blueprint = BP; + for (Var &V : Variables) + { + FBPVariableDescription *Desc = WingUtils::FindOneWithInternalID(V.Name, BP->NewVariables, TEXT("non-inherited variable")); + if (Desc == nullptr) return false; + V.BPVar = Desc; + } + Blueprint = BP; + return true; +} + +bool WingVariables::AssociateFunctionLocalVariables(UK2Node_EditablePinBase *Node) +{ + Category = Cat::FunctionLocal; + if (UK2Node_FunctionEntry *Func = Cast(Node)) + { + for (Var &V : Variables) + { + FBPVariableDescription *Desc = WingUtils::FindOneWithInternalID(V.Name, Func->LocalVariables, TEXT("local variable")); + if (Desc == nullptr) return false; + V.BPVar = Desc; + } + return true; + } + else + { + UWingServer::Printf(TEXT("Graph can't have local variables, not a function graph: %s\n"), + *WingUtils::FormatName(Node->GetGraph())); + return false; + } +} + +bool WingVariables::AssociateEditablePinBase(UK2Node_EditablePinBase *InPinBase) +{ + if (!InPinBase || !InPinBase->IsEditable()) + { + UWingServer::Print(TEXT("ERROR: Node does not have editable pins.\n")); + return false; + } + Category = Cat::EditablePinBase; + PinBase = InPinBase; + for (Var &V : Variables) + { + TSharedPtr *Found = WingUtils::FindOneWithInternalID(V.Name, InPinBase->UserDefinedPins, TEXT("pin")); + if (!Found) return false; + V.Pin = *Found; + } + return true; +} + +void WingVariables::UpdateVariableTypes() +{ + switch (Category) + { + case Cat::Blueprint: UpdateBlueprintVariableTypes(); break; + case Cat::FunctionLocal: UpdateLocalVariableTypes(); break; + case Cat::EditablePinBase: UpdateEditablePinBaseTypes(); break; + default: return; + } +} + +void WingVariables::UpdateVariableFlags() +{ + switch (Category) + { + case Cat::Blueprint: UpdateBlueprintVariableFlags(); break; + case Cat::FunctionLocal: UpdateLocalVariableFlags(); break; + case Cat::EditablePinBase: UpdateEditablePinBaseFlags(); break; + default: return; + } +} + +bool WingVariables::UpdateVariableDefaults() +{ + switch (Category) + { + case Cat::Blueprint: return UpdateBlueprintVariableDefaults(); break; + case Cat::FunctionLocal: return UpdateLocalVariableDefaults(); break; + case Cat::EditablePinBase: return UpdateEditablePinBaseDefaults(); break; + default: return true; + } +} + + bool WingVariables::CheckSanity(Cat Category) { const TSet &Relevant = GetRelevantFlagSet(Category); @@ -94,42 +236,113 @@ bool WingVariables::CheckSanity(Cat Category) } -WingVariables WingVariables::ParseBlueprintVariables(const UBlueprint *BP) +bool WingVariables::ParseVariableFlags(WingTokenizer &Tok, TSet &Out) { - WingVariables Result; - for (const FBPVariableDescription& Desc : BP->NewVariables) + Tok.Advance(); // Step over open-paren + while (Tok.TokenIs(Tok.Identifier)) { - // Skip event dispatchers. - if (Desc.VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate) continue; - - // Parse the bulk of the flags. - Var V = ParseVariableDescription(Desc, Cat::Blueprint); - - // Read default value from CDO if available. - if (BP->GeneratedClass) - { - UObject* CDO = BP->GeneratedClass->GetDefaultObject(); - FProperty* Prop = BP->GeneratedClass->FindPropertyByName(Desc.VarName); - if (CDO && Prop) - V.DefaultValue = FWingProperty(Prop, CDO).GetText(); - } - - Result.Variables.Add(MoveTemp(V)); + Out.Add(Tok.NextName()); + Tok.Advance(); + // Commas are optional. + if (Tok.TokenIs(',')) Tok.Advance(); } - return Result; + if (!Tok.TokenIs(')')) + { + Tok.SaveCursor(NAME_None); + UWingServer::Printf(TEXT("ERROR: flag list contains invalid token '%s'\n"), + *FString(Tok.GetRange(NAME_None, 1))); + return false; + } + Tok.Advance(); // Step over close-paren + return true; } -WingVariables WingVariables::ParseFunctionLocalVariables(const UK2Node_FunctionEntry *Func) +bool WingVariables::ParseOneVariable(WingTokenizer &Tok, Var &V) { - WingVariables Result; - if (!Func) return Result; + // Parse type. + UWingTypes::Requirements Req; + Req.BlueprintType = true; + Req.Blueprintable = false; + Req.AllowContainer = true; + if (!UWingTypes::TextToType(Tok, V.Type, Req, false)) + return false; - for (const FBPVariableDescription& Desc : Func->LocalVariables) + // Parse name. + if (Tok.NextType() != Tok.Identifier) { - Var V = ParseVariableDescription(Desc, Cat::FunctionLocal); - V.DefaultValue = Desc.DefaultValue; - Result.Variables.Add(MoveTemp(V)); + UWingServer::Print(TEXT("ERROR: Expected variable name after type\n")); + return false; } + V.Name = Tok.NextName(); + Tok.Advance(); + + // Parse optional flags: (flag1, flag2, ...) + if (Tok.TokenIs('(')) + { + if (!ParseVariableFlags(Tok, V.Flags)) return false; + } + + // Parse optional default value: = rest-of-line + if (Tok.NextType() == Tok.RestOfLine) + { + V.DefaultSpecified = true; + V.DefaultValue = FString(Tok.NextRest().TrimStartAndEnd()); + Tok.Advance(); + } + + // Should be at end of line. + if (Tok.NextType() != 0) + { + Tok.SaveCursor(NAME_None); + UWingServer::Printf(TEXT("ERROR: Unexpected token after variable declaration: '%s'\n"), + *FString(Tok.GetRange(NAME_None, 1))); + return false; + } + + return true; +} + +bool WingVariables::ParseString(const FString &Input) +{ + Variables.Empty(); + + TArray Lines; + Input.ParseIntoArrayLines(Lines); + + for (const FString& Line : Lines) + { + WingTokenizer Tok(Line); + if (Tok.NextType() == 0) continue; + Var V; + V.DefaultSpecified = false; + if (!ParseOneVariable(Tok, V)) return false; + Variables.Add(MoveTemp(V)); + } + + return true; +} + +WingVariables::Var WingVariables::ParseVariableDescription(const FBPVariableDescription &Desc, WingVariables::Cat Category) +{ + Var Result; + Result.Name = Desc.VarName; + Result.Type = Desc.VarType; + + if (!(Desc.PropertyFlags & CPF_DisableEditOnInstance)) + Result.AddFlagIfRelevant(Flag_InstanceEditable, Category); + + if (Desc.PropertyFlags & CPF_BlueprintReadOnly) + Result.AddFlagIfRelevant(Flag_BlueprintReadOnly, Category); + + if (Desc.PropertyFlags & CPF_Interp) + Result.AddFlagIfRelevant(Flag_ExposeToCinematics, Category); + + if (Desc.HasMetaData(FBlueprintMetadata::MD_ExposeOnSpawn)) + Result.AddFlagIfRelevant(Flag_ExposeOnSpawn, Category); + + if (Desc.HasMetaData(FBlueprintMetadata::MD_Private)) + Result.AddFlagIfRelevant(Flag_Private, Category); + return Result; } @@ -170,3 +383,154 @@ void WingVariables::PrintAll(int32 Indent) { PrintAll(UWingServer::GetPrintBuffer(), Indent); } + +void WingVariables::UpdateBlueprintVariableTypes() +{ + for (const Var &Input : Variables) + { + Input.BPVar->VarType = Input.Type; + } +} + +void WingVariables::UpdateBlueprintVariableFlags() +{ + for (const Var &Input : Variables) + { + FBPVariableDescription &Out = *Input.BPVar; + + // InstanceEditable: absence means CPF_DisableEditOnInstance is set + if (Input.Flags.Contains(Flag_InstanceEditable)) + Out.PropertyFlags &= ~CPF_DisableEditOnInstance; + else + Out.PropertyFlags |= CPF_DisableEditOnInstance; + + if (Input.Flags.Contains(Flag_BlueprintReadOnly)) + Out.PropertyFlags |= CPF_BlueprintReadOnly; + else + Out.PropertyFlags &= ~CPF_BlueprintReadOnly; + + if (Input.Flags.Contains(Flag_ExposeToCinematics)) + Out.PropertyFlags |= CPF_Interp; + else + Out.PropertyFlags &= ~CPF_Interp; + + if (Input.Flags.Contains(Flag_ExposeOnSpawn)) + Out.SetMetaData(FBlueprintMetadata::MD_ExposeOnSpawn, TEXT("true")); + else + Out.RemoveMetaData(FBlueprintMetadata::MD_ExposeOnSpawn); + + if (Input.Flags.Contains(Flag_Private)) + Out.SetMetaData(FBlueprintMetadata::MD_Private, TEXT("true")); + else + Out.RemoveMetaData(FBlueprintMetadata::MD_Private); + } +} + +bool WingVariables::UpdateBlueprintVariableDefaults() +{ + UObject *CDO = nullptr; + if (Blueprint->GeneratedClass) CDO = Blueprint->GeneratedClass->GetDefaultObject(); + if (CDO == nullptr) + { + UWingServer::Printf(TEXT("Blueprint is not compiled, cannot update variable defaults.")); + return false; + } + int OK = true; + for (const Var &Input : Variables) + { + if (Input.DefaultSpecified) + { + FProperty* Prop = Blueprint->GeneratedClass->FindPropertyByName(Input.Name); + if (!Prop) + { + UWingServer::Printf(TEXT("Variable exists in blueprint, but not the generated class, which is weird: %s."), + *WingTokenizer::ExternalizeID(Input.Name)); + return false; + } + if (!FWingProperty(Prop, CDO).SetText(Input.DefaultValue)) OK = false; + } + } + return OK; +} + +void WingVariables::UpdateLocalVariableTypes() +{ + for (const Var &Input : Variables) + { + Input.BPVar->VarType = Input.Type; + } +} + +void WingVariables::UpdateLocalVariableFlags() +{ + for (const Var &Input : Variables) + { + FBPVariableDescription &Out = *Input.BPVar; + // Currently, no supported flags for local variables. + } +} + +bool WingVariables::UpdateLocalVariableDefaults() +{ + for (const Var &Input : Variables) + { + if (Input.DefaultSpecified) + { + Input.BPVar->DefaultValue = Input.DefaultValue; + } + } + return true; +} + +void WingVariables::UpdateEditablePinBaseTypes() +{ + for (const Var &Input : Variables) + Input.Pin->PinType = Input.Type; + PinBase->ReconstructNode(); +} + +void WingVariables::UpdateEditablePinBaseFlags() +{ + // Currently no flags apply to editable pin base pins. +} + +bool WingVariables::UpdateEditablePinBaseDefaults() +{ + for (const Var &Input : Variables) + { + if (Input.DefaultSpecified) + Input.Pin->PinDefaultValue = Input.DefaultValue; + } + PinBase->ReconstructNode(); + return true; +} + +void WingGraphVariables::ParseGraph(const UEdGraph *Graph) +{ + TWeakObjectPtr EntryNode; + TWeakObjectPtr ResultNode; + FBlueprintEditorUtils::GetEntryAndResultNodes(Graph, EntryNode, ResultNode); + Arguments = WingVariables::ParseEditablePinBase(EntryNode.Get()); + ReturnValues = WingVariables::ParseEditablePinBase(ResultNode.Get()); + LocalVariables = WingVariables::ParseLocalVariables(EntryNode.Get()); +} + +bool WingGraphVariables::ParseAll(const FString &A, const FString &R, const FString &L) +{ + if (!Arguments.ParseString(A)) return false; + if (!ReturnValues.ParseString(R)) return false; + if (!LocalVariables.ParseString(L)) return false; + return true; +} + +bool WingGraphVariables::AssociateAll(const UEdGraph *Graph) +{ + TWeakObjectPtr EntryNode; + TWeakObjectPtr ResultNode; + FBlueprintEditorUtils::GetEntryAndResultNodes(Graph, EntryNode, ResultNode); + bool OK = true; + if (!Arguments.AssociateEditablePinBase(EntryNode.Get())) OK = false; + if (!ReturnValues.AssociateEditablePinBase(ResultNode.Get())) OK = false; + if (!LocalVariables.AssociateLocalVariables(EntryNode.Get())) OK = false; + return OK; +} diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingTokenizer.h b/Plugins/UEWingman/Source/UEWingman/Public/WingTokenizer.h index 2ac4b7ed..326c5590 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingTokenizer.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingTokenizer.h @@ -103,13 +103,15 @@ struct WingTokenizer // ////////////////////////////////////////////////////////////////////////// - const TCHAR Identifier = 'i'; - const TCHAR RestOfLine = 'r'; + static const TCHAR Identifier = 'i'; + static const TCHAR AssetName = 'a'; + static const TCHAR RestOfLine = 'r'; // Get the next token. TCHAR NextType() const { return Next[0].Type; } FName NextName() const { return Next[0].Name; } FStringView NextRest() const { return Next[0].Rest; } + FStringView NextSource() const { return Next[0].Source; } // Check the next token. bool TokenIs(TCHAR Type) const { return Next[0].Type == Type; } @@ -122,9 +124,13 @@ struct WingTokenizer // Advance the cursor. Don't move past the two sentinels. void Advance() { int I = Next-Tokens.GetData(); if (I + 2 < Tokens.Num()) Next++; } - - // Get the original input string. - const FString& GetInput() const { return Input; } + + // Save the current cursor position, giving the save a name. + void SaveCursor(FName Name); + + // Get the input text from a saved cursor position to the current + // cursor, optionally including additional tokens. + FStringView GetRange(FName SavePos, int32 Extra = 0) const; // Tokenize a line of input. The tokens are stored in // the token array, and the cursor is positioned on the first @@ -176,8 +182,9 @@ private: struct Token { FName Name; // Only if it's an identifier token. - FStringView Rest; // Only if it's a rest-of-line token. + FStringView Rest; // Only if it's a rest-of-line token or Asset. TCHAR Type = 0; // A punctuation mark, or Identifier, or RestOfLine + FStringView Source; // The span of input that produced this token. }; // The string that we tokenized. @@ -192,6 +199,9 @@ private: // The cursor which advances along the tokens. Never moves past sentinels. Token *Next; + // A table of saved cursor positions. + TArray, TInlineAllocator<3>> SavedCursor; + ////////////////////////////////////////////////////////////////////////// // // Internal Implementation Functions. @@ -200,15 +210,22 @@ private: using Cat = WingCharacterClasses::Cat; - // Add a token to the token array. - void Add(TCHAR Type, FName InternalID); - void Add(TCHAR Type, FStringView Rest); + // Add a token to the token array. Before/After are the Rest + // string view before and after tokenization; the difference + // is stored as the Source span. + void Add(TCHAR Type, FName InternalID, FStringView Before, FStringView After); + void Add(TCHAR Type, FStringView Rest, FStringView Before, FStringView After); // Convert numbers to TCHAR. If there's an error, set the error // message and return zero. static TCHAR FromHex(FStringView Digits, FString &Error); static TCHAR FromDecimal(FStringView Digits, FString &Error); + // Tokenize an asset name. Attempt to consume an asset name from rest, + // and return the asset name as a string view. On error, + // sets the error message and returns the empty string view. + static FStringView TokenizeAssetName(FStringView &Rest, FString &Error); + // Tokenize an escape sequence. Attempts to consume a valid escape // sequence from rest, and return the character indicated. On error, // sets the error message and returns zero. diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingTypes.h b/Plugins/UEWingman/Source/UEWingman/Public/WingTypes.h index 970b8719..4b2db891 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingTypes.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingTypes.h @@ -118,7 +118,6 @@ private: // --------------------------------------------------------------------------- static void PrintParseError(WingTokenizer& Tok, const TCHAR* Message); - static bool ParseEOF(WingTokenizer& Tok); static bool ParseChar(WingTokenizer& Tok, TCHAR c); bool ParsePlainIdentifier(WingTokenizer& Tok, FEdGraphPinType& OutType); bool ParseWrapped(WingTokenizer& Tok, FName Wrapper, FEdGraphPinType& OutType); @@ -144,6 +143,11 @@ public: }; + // Parse a type. If it doesn't parse, or if the type doesn't satisfy the + // requirements, prints an error and returns false. If CheckEOF is true, + // makes sure that the input is *just* a type and nothing else after it. + static bool TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, const Requirements &Require, bool CheckEOF); + // Parse a type. If it doesn't parse, or if the type doesn't satisfy the // requirements, prints an error and returns false. static bool TextToType(const FString& Text, FEdGraphPinType& OutPinType, const Requirements &Require); diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h index 3e06b023..5415758c 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h @@ -14,6 +14,7 @@ #include "K2Node_EditablePinBase.h" #include "Components/Widget.h" #include "WingActorComponent.h" +#include "WingVariables.h" struct FEdGraphSchemaAction; class UAnimationStateMachineGraph; @@ -60,8 +61,10 @@ public: static FName GetFName(const FProperty *Prop) { return Prop->GetFName(); } static FName GetFName(const FWingProperty &Prop); static FName GetFName(const FUserPinInfo &Pin) { return Pin.PinName; } + static FName GetFName(const TSharedPtr &Pin) { return Pin->PinName; } static FName GetFName(const UWingComponentReference *Ref) { return Ref->VariableName; } static FName GetFName(const UWidget *Widget) { return Widget->GetFName(); } + static FName GetFName(const WingVariables::Var &Var) { return Var.Name; } //////////////////////////////////////////////////////// // @@ -132,6 +135,21 @@ public: return FindNoneWithInternalID(InternalID, Array, Kind); } + static bool FindNoDuplicateName(TSet &Collection, FName InternalID, const TCHAR *Kind) + { + if (Collection.Contains(InternalID)) + { CheckExactlyOneNamed(2, Kind, InternalID); return false; } + return true; + } + + template + static bool FindNoDuplicateNames(TSet &Collection, ArrayType &&Array, const TCHAR *Kind) + { + for (auto &Elt : Array) + if (!FindNoDuplicateName(Collection, GetFName(Elt), Kind)) return false; + return true; + } + //////////////////////////////////////////////////////// // // Name Formatting diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingVariables.h b/Plugins/UEWingman/Source/UEWingman/Public/WingVariables.h index 1526b2e3..fa1cb9d2 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingVariables.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingVariables.h @@ -1,20 +1,23 @@ #pragma once #include "CoreMinimal.h" -#include "WingProperty.h" +#include "EdGraph/EdGraphPin.h" #include "Engine/Blueprint.h" +#include "K2Node_EditablePinBase.h" -class UK2Node_FunctionEntry; +struct WingTokenizer; class WingVariables { +public: enum class Cat { + Empty, Blueprint, - FunctionLocal + LocalVariable, + EditablePinBase, }; -private: struct Var { // Internal name. @@ -26,18 +29,41 @@ private: // Default Value. FString DefaultValue; + // When parsing a string, true if default was specified. + bool DefaultSpecified = true; + // Boolean flags. TSet Flags; + // This is only populated if these variables are associated + // with blueprint variables or local variables. + FBPVariableDescription *BPVar = nullptr; + + // This is only populated if these variables are associated + // with an editable pin base. + TSharedPtr Pin = nullptr; + // Add the flag if it's relevant to category C. void AddFlagIfRelevant(FName Flag, Cat C); }; private: + // The category of these variables. + Cat Category; + // A list of all the variables. - TArray Variables; + TArray Variables; + + // A pointer to a blueprint. + UBlueprint *Blueprint = nullptr; + + // A pointer to an editable pin base. + UK2Node_EditablePinBase* PinBase = nullptr; public: + // Get the variables. + const TArray GetVariables() { return Variables; } + // Returns the set of flags that are supported by this variable category. static const TSet &GetRelevantFlagSet(Cat C); @@ -53,11 +79,52 @@ public: // Make sure that this variable list makes sense for the given context. bool CheckSanity(Cat Category); + // Parse variables from a string. One variable per line. + // Format: type name (flag1, flag2) = defaultvalue + // Returns false on parse error. + bool ParseString(const FString &Input); + // Parse variables from a given source. - static WingVariables ParseBlueprintVariables(const UBlueprint *BP); - static WingVariables ParseFunctionLocalVariables(const UK2Node_FunctionEntry *F); + static WingVariables ParseBlueprintVariables(UBlueprint *BP); + static WingVariables ParseLocalVariables(UK2Node_EditablePinBase* Node); + static WingVariables ParseEditablePinBase(UK2Node_EditablePinBase* Node); + + // Associate every variable in the list with an existing blueprint variable. + bool AssociateBlueprintNewVariables(UBlueprint *BP); + bool AssociateLocalVariables(UK2Node_EditablePinBase* Node); + bool AssociateEditablePinBase(UK2Node_EditablePinBase* Node); + + // Copy data from these variables into their associated blueprint variables. + void UpdateVariableTypes(); + void UpdateVariableFlags(); + bool UpdateVariableDefaults(); private: static Var ParseVariableDescription(const FBPVariableDescription &V, Cat C); + static bool ParseOneVariable(WingTokenizer &Tok, Var &Out); + static bool ParseVariableFlags(WingTokenizer &Tok, TSet &Out); + + void UpdateBlueprintVariableTypes(); + void UpdateBlueprintVariableFlags(); + bool UpdateBlueprintVariableDefaults(); + void UpdateLocalVariableTypes(); + void UpdateLocalVariableFlags(); + bool UpdateLocalVariableDefaults(); + void UpdateEditablePinBaseTypes(); + void UpdateEditablePinBaseFlags(); + bool UpdateEditablePinBaseDefaults(); +}; + +struct WingGraphVariables +{ + WingVariables Arguments; + WingVariables ReturnValues; + WingVariables LocalVariables; + + WingGraphVariables() {} + + void ParseGraph(const UEdGraph *Graph); + bool ParseStrings(const FString &A, const FString &R, const FString &L); + bool AssociateGraph(const UEdGraph *Graph); }; diff --git a/Plugins/UEWingman/ue-wingman.py b/Plugins/UEWingman/ue-wingman.py index c2248e07..31ab61fe 100755 --- a/Plugins/UEWingman/ue-wingman.py +++ b/Plugins/UEWingman/ue-wingman.py @@ -20,19 +20,38 @@ def main(): print("Usage: ue-wingman.py ShowCommands [key=value ...]") sys.exit(1) - msg = {"command": args[0]} - for arg in args[1:]: - key, _, value = arg.partition("=") - if value.lower() == "true": - value = True - elif value.lower() == "false": - value = False - else: + no_args_commands = {"ShowCommands", "UserManual"} + if len(args) == 1 and args[0] not in no_args_commands: + # No extra arguments: read JSON object from stdin. + # Accumulate lines and try to parse after each one. + decoder = json.JSONDecoder() + raw = "" + msg = None + for line in sys.stdin: + raw += line try: - value = int(value) - except ValueError: - pass - msg[key] = value + msg, _ = decoder.raw_decode(raw.lstrip()) + break + except json.JSONDecodeError: + continue + if msg is None: + print("Could not parse a complete JSON object from stdin") + sys.exit(1) + msg["command"] = args[0] + else: + msg = {"command": args[0]} + for arg in args[1:]: + key, _, value = arg.partition("=") + if value.lower() == "true": + value = True + elif value.lower() == "false": + value = False + else: + try: + value = int(value) + except ValueError: + pass + msg[key] = value sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(TIMEOUT)