From 93ce030fc0f47630fc081ca4febd376a91f3f692 Mon Sep 17 00:00:00 2001 From: jyelon Date: Tue, 31 Mar 2026 03:50:06 -0400 Subject: [PATCH] More progress on variables --- Content/Testing/BP_Test.uasset | 4 +- .../Handlers/BlueprintVariable_Create.h | 37 +- .../Handlers/BlueprintVariable_Dump.h | 7 +- .../Handlers/BlueprintVariable_Modify.h | 18 +- .../UEWingman/Handlers/Blueprint_Dump.h | 6 +- .../Handlers/GraphVariables_Create.h | 57 ++ .../UEWingman/Handlers/GraphVariables_Dump.h | 6 +- .../Handlers/GraphVariables_Modify.h | 24 +- .../UEWingman/Private/WingGraphExport.cpp | 6 +- .../Source/UEWingman/Private/WingUtils.cpp | 6 + .../UEWingman/Private/WingVariables.cpp | 803 +++++++++--------- .../Source/UEWingman/Public/WingUtils.h | 3 +- .../Source/UEWingman/Public/WingVariables.h | 189 ++--- 13 files changed, 588 insertions(+), 578 deletions(-) create mode 100644 Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Create.h diff --git a/Content/Testing/BP_Test.uasset b/Content/Testing/BP_Test.uasset index f778c109..fb02e8ed 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:906f25718c06e1253e323913ff875c9f6f0f59b637b3a9def20ab6cdd8456060 -size 45363 +oid sha256:b5ae97431b3c210858449639e2c4f3073037db42cda99d77881327d3154b67e5 +size 45291 diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Create.h index feefa18b..44d6f7b2 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Create.h @@ -12,7 +12,6 @@ #include "Kismet2/KismetEditorUtilities.h" #include "BlueprintVariable_Create.generated.h" - // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- @@ -23,15 +22,16 @@ class UWing_BlueprintVariable_Create : public UObject, public IWingHandler GENERATED_BODY() public: - UPROPERTY(meta=(Description="Blueprint name or package path")) + UPROPERTY(meta=(Description="Blueprint path")) FString Blueprint; - UPROPERTY(meta=(Description="Variable declarations, one per line. Format: type name (flags) = default")) + UPROPERTY(meta=(Description="Variable declarations")) FString Variables; virtual FString GetDescription() const override { - return TEXT("Add new member variables to a Blueprint. Format: 'type name (flags) = default', one per line."); + return TEXT("Add new member variables to a Blueprint. " + "Format: 'type name (flags) = default', one per line."); } virtual void Handle() override @@ -41,31 +41,10 @@ public: if (!BP) return; // Parse the variable declarations. - WingVariables Vars; - if (!Vars.ParseString(Variables)) return; - if (Vars.IsEmpty()) { UWingServer::Print(TEXT("ERROR: No variables specified.\n")); return; } - - // Check for name collisions against existing variables, components, and the like. - TSet Names; - FBlueprintEditorUtils::GetClassVariableList(BP, Names); - if (!WingUtils::FindNoDuplicateNames(Names, Vars.GetVariables(), TEXT("variable or component"))) return; - - // Create the variables. - for (const WingVariables::Var& V : Vars.GetVariables()) - { - if (!FBlueprintEditorUtils::AddMemberVariable(BP, V.Name, V.Type)) - { - UWingServer::Printf(TEXT("ERROR: Failed to add variable '%s'\n"), - *WingUtils::ExternalizeID(V.Name)); - return; - } - } - - // Update everything. - if (!Vars.AssociateBlueprintVariables(BP)) return; - Vars.UpdateVariableFlags(); - FKismetEditorUtilities::CompileBlueprint(BP); - if (!Vars.UpdateVariableDefaults()) return; + WingVariables Vars(BP); + if (!Vars.BlueprintVariables.ParseString(Variables)) return; + if (!Vars.Check()) return; + if (!Vars.Create()) 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 8b440b7d..3fe59cd0 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Dump.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Dump.h @@ -32,9 +32,8 @@ public: UBlueprint* BP = F.Walk(Blueprint).Cast(); if (!BP) return; - WingVariables Vars; - Vars.LoadBlueprintVariables(BP); - if (Vars.IsEmpty()) { UWingServer::Print(TEXT("No variables.\n")); return; } - Vars.PrintAll(UWingServer::GetPrintBuffer(), true, TEXT("Blueprint Variables:")); + WingVariables Vars(BP); + Vars.Load(); + Vars.Print(UWingServer::GetPrintBuffer()); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Modify.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Modify.h index 9c1b02ee..873568f4 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Modify.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Modify.h @@ -39,20 +39,10 @@ public: if (!BP) return; // Parse the variable declarations. - WingVariables Vars; - if (!Vars.ParseString(Variables)) return; - if (Vars.IsEmpty()) { UWingServer::Print(TEXT("ERROR: No variables specified.\n")); return; } - if (!Vars.CheckSanity(WingVariables::Cat::BlueprintVariables)) return; - - // Associate with existing blueprint variables. - if (!Vars.AssociateBlueprintVariables(BP)) return; - - // Update types and flags, compile, then update defaults. - Vars.UpdateVariableTypes(); - Vars.UpdateVariableFlags(); - FKismetEditorUtilities::CompileBlueprint(BP); - if (!Vars.UpdateVariableDefaults()) return; - + WingVariables Vars(BP); + if (!Vars.BlueprintVariables.ParseString(Variables)) return; + if (!Vars.Check()) return; + if (!Vars.Modify()) return; UWingServer::Printf(TEXT("Success.\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h index bd358704..52781c0d 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h @@ -84,9 +84,9 @@ public: } // Variables (new format) - WingVariables BlueprintVars; - BlueprintVars.LoadBlueprintVariables(BP); - BlueprintVars.PrintAll(UWingServer::GetPrintBuffer(), true, TEXT("Blueprint Variables 2:")); + WingVariables BlueprintVars(BP); + BlueprintVars.Load(); + BlueprintVars.Print(UWingServer::GetPrintBuffer()); // Components TArray Components3 = UWingComponentReference::GetAll(BP); diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Create.h new file mode 100644 index 00000000..54d0a445 --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Create.h @@ -0,0 +1,57 @@ +#pragma once + +#include "CoreMinimal.h" +#include "WingHandler.h" +#include "WingServer.h" +#include "WingFetcher.h" +#include "WingVariables.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "K2Node_EditablePinBase.h" +#include "K2Node_FunctionEntry.h" +#include "GraphVariables_Create.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UWing_GraphVariables_Create : public UObject, public IWingHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Path to a function or macro graph (e.g. '/Game/MyBP,graph:MyFunction')")) + FString Graph; + + UPROPERTY(meta=(Optional, Description="Inputs to the graph")) + FString InputVariables; + + UPROPERTY(meta=(Optional, Description="Outputs to the graph")) + FString OutputVariables; + + UPROPERTY(meta=(Optional, Description="Locals to the graph")) + FString LocalVariables; + + virtual FString GetDescription() const override + { + return TEXT("Add new inputs, outputs, and local variables to a graph. " + "Format: 'type name (flags) = default', one per line"); + } + + virtual void Handle() override + { + WingFetcher F; + UEdGraph* G = F.Walk(Graph).Cast(); + if (!G) return; + + WingVariables Vars(G); + if (!Vars.InputVariables.ParseString(InputVariables)) return; + if (!Vars.OutputVariables.ParseString(OutputVariables)) return; + if (!Vars.LocalVariables.ParseString(LocalVariables)) return; + if (!Vars.Check()) return; + if (!Vars.Create()) return; + 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 index f80ad03f..c3756b08 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Dump.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Dump.h @@ -33,8 +33,8 @@ public: UEdGraph* G = F.Walk(Graph).Cast(); if (!G) return; - WingGraphVariables Vars; - Vars.LoadGraph(G); - Vars.PrintAll(UWingServer::GetPrintBuffer(), true); + WingVariables Vars(G); + Vars.Load(); + Vars.Print(UWingServer::GetPrintBuffer()); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Modify.h b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Modify.h index c8f2c864..6887f518 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Modify.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphVariables_Modify.h @@ -21,13 +21,13 @@ public: UPROPERTY(meta=(Description="Path to a graph")) FString Graph; - UPROPERTY(meta=(Optional, Description="Argument declarations, one per line.")) - FString Arguments; + UPROPERTY(meta=(Optional, Description="Inputs to the graph")) + FString InputVariables; - UPROPERTY(meta=(Optional, Description="Return Value declarations, one per line.")) - FString ReturnValues; + UPROPERTY(meta=(Optional, Description="Outputs to the graph")) + FString OutputVariables; - UPROPERTY(meta=(Optional, Description="Local Variable declarations, one per line.")) + UPROPERTY(meta=(Optional, Description="Locals to the graph")) FString LocalVariables; virtual FString GetDescription() const override @@ -41,14 +41,12 @@ public: UEdGraph* G = F.Walk(Graph).Cast(); if (!G) return; - WingGraphVariables Vars; - if (!Vars.ParseStrings(Arguments, ReturnValues, LocalVariables)) return; - if (!Vars.CheckSanity(G)) return; - if (!Vars.AssociateGraph(G)) return; - Vars.UpdateVariableTypes(); - Vars.UpdateVariableFlags(); - Vars.UpdateVariableDefaults(); - + WingVariables Vars(G); + if (!Vars.InputVariables.ParseString(InputVariables)) return; + if (!Vars.OutputVariables.ParseString(OutputVariables)) return; + if (!Vars.LocalVariables.ParseString(LocalVariables)) return; + if (!Vars.Check()) return; + if (!Vars.Modify()) return; UWingServer::Printf(TEXT("Success.\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp index c327a4d1..6b4cde7f 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp @@ -289,9 +289,9 @@ void WingGraphExport::EmitMaterialProperties(UEdGraphNode* Node, FStringBuilderB void WingGraphExport::EmitLocalVariables() { - WingGraphVariables Vars; - Vars.LoadGraph(Graph); - Vars.PrintAll(Output, false); + WingVariables Vars(Graph); + Vars.Load(); + Vars.Print(Output); } void WingGraphExport::EmitGraph() diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp index ff0882ec..476bdb0e 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp @@ -476,6 +476,12 @@ TArray WingUtils::GetAncestorBlueprints(UBlueprint *BP, bool Oldest return Blueprints; } +UObject *WingUtils::GetGeneratedCDO(UBlueprint *BP) +{ + if (BP->GeneratedClass == nullptr) return nullptr; + return BP->GeneratedClass->GetDefaultObject(); +} + // ============================================================ // Material helpers // ============================================================ diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingVariables.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingVariables.cpp index 5d054189..e92dea1d 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingVariables.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingVariables.cpp @@ -10,6 +10,7 @@ #include "K2Node_Tunnel.h" #include "K2Node_EditablePinBase.h" #include "Kismet2/BlueprintEditorUtils.h" +#include "Kismet2/KismetEditorUtilities.h" // Flag names used for blueprint variables. static const FName Flag_InstanceEditable(TEXT("InstanceEditable")); @@ -22,197 +23,58 @@ static const FName Flag_ExposeToCinematics(TEXT("ExposeToCinematics")); static TSet Flags_None = { }; static TSet Flags_BlueprintVariables = { Flag_InstanceEditable, Flag_BlueprintReadOnly, Flag_ExposeOnSpawn, Flag_Private, Flag_ExposeToCinematics }; -const TSet &WingVariables::GetRelevantFlagSet(WingVariables::Cat C) + + +void WingVariableList::Print(FStringBuilderBase &Out) { - switch (C) + if (Variables.IsEmpty()) return; + Out.Append(ListName); + Out.AppendChar(':'); + Out.AppendChar('\n'); + for (const Var& V : Variables) { - case Cat::BlueprintVariables: return Flags_BlueprintVariables; - case Cat::LocalVariables: return Flags_None; - case Cat::FunctionArguments: return Flags_None; - case Cat::FunctionReturnValues: return Flags_None; - case Cat::MacroEntry: return Flags_None; - case Cat::MacroExit: return Flags_None; + FString TypeStr = UWingTypes::TypeToText(V.Type); + FString NameStr = WingUtils::ExternalizeID(V.Name); + + // Build flags string. + FString FlagsStr; + if (V.Flags.Num() > 0) + { + TArray Sorted = V.Flags.Array(); + Sorted.Sort(FNameLexicalLess()); + for (const FName& Flag : Sorted) + { + if (!FlagsStr.IsEmpty()) FlagsStr += TEXT(", "); + FlagsStr += Flag.ToString(); + } + } + + // Print: type name (flags) = defaultvalue + Out.Appendf(TEXT(" %s %s"), *TypeStr, *NameStr); + if (!FlagsStr.IsEmpty()) + Out.Appendf(TEXT(" (%s)"), *FlagsStr); + if (!V.DefaultValue.IsEmpty()) + Out.Appendf(TEXT(" = %s"), *V.DefaultValue); + Out.Append(TEXT("\n")); } } - -void WingVariables::Var::AddFlagIfRelevant(FName Flag, WingVariables::Cat C) + +void WingVariableList::ClearLinks() { - if (GetRelevantFlagSet(C).Contains(Flag)) Flags.Add(Flag); -} - -void WingVariables::Clear() -{ - Variables.Empty(); - Blueprint = nullptr; - FuncEntry = nullptr; - PinBase = nullptr; -} - -void WingVariables::ClearAssociation() -{ - for (Var V : Variables) + for (Var &V : Variables) { V.BPVar = nullptr; V.Pin = nullptr; } - Blueprint = nullptr; - FuncEntry = nullptr; - PinBase = nullptr; } -void WingVariables::LoadBlueprintVariables(UBlueprint *BP) +bool WingVariableList::CheckSanity(const TSet &GoodFlags, bool Allow) { - Clear(); - for (FBPVariableDescription& Desc : BP->NewVariables) + if ((!Allow) && (!Variables.IsEmpty())) { - // Skip event dispatchers. - if (Desc.VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate) continue; - - // Parse the bulk of the flags. - Var V = ParseVariableDescription(Desc, Cat::BlueprintVariables); - - // 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(); - V.DefaultSpecified = true; - } - } - - Variables.Add(MoveTemp(V)); - } -} - -void WingVariables::LoadLocalVariables(UK2Node_EditablePinBase *Node) -{ - Clear(); - if (UK2Node_FunctionEntry *Func = Cast(Node)) - { - for (FBPVariableDescription& Desc : Func->LocalVariables) - { - Var V = ParseVariableDescription(Desc, Cat::LocalVariables); - V.DefaultValue = Desc.DefaultValue; - V.DefaultSpecified = true; - Variables.Add(MoveTemp(V)); - } - } -} - -void WingVariables::LoadEditablePinBase(UK2Node_EditablePinBase* Node) -{ - Clear(); - if (Node != nullptr) - { - for (const TSharedPtr& PinInfo : Node->UserDefinedPins) - { - Var V; - V.Name = PinInfo->PinName; - V.Type = PinInfo->PinType; - V.DefaultValue = PinInfo->PinDefaultValue; - V.DefaultSpecified = true; - Variables.Add(MoveTemp(V)); - } - } -} - -bool WingVariables::AssociateBlueprintVariables(UBlueprint *BP) -{ - ClearAssociation(); - 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; - } - return true; -} - -bool WingVariables::AssociateLocalVariables(UK2Node_EditablePinBase *Node) -{ - ClearAssociation(); - if (UK2Node_FunctionEntry *Func = Cast(Node)) - { - FuncEntry = Func; - 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 - { - if (Variables.IsEmpty()) { return true; } - UWingServer::Printf(TEXT("Graph can't have local variables, not a function graph: %s\n"), - *WingUtils::FormatName(Node->GetGraph())); + UWingServer::Printf(TEXT("In this context, %s must be empty."), ListName); return false; } -} - -bool WingVariables::AssociateEditablePinBase(UK2Node_EditablePinBase *Node) -{ - ClearAssociation(); - if (Node != nullptr) - { - PinBase = Node; - for (Var &V : Variables) - { - TSharedPtr *Found = WingUtils::FindOneWithInternalID(V.Name, Node->UserDefinedPins, TEXT("pin")); - if (!Found) return false; - V.Pin = *Found; - } - } - else - { - if (Variables.IsEmpty()) return true; - UWingServer::Printf(TEXT("Cannot modify variables, graph node does not exist yet")); - return false; - } - return true; -} - -void WingVariables::UpdateVariableTypes() -{ - if (Blueprint) return UpdateBlueprintVariableTypes(); - if (FuncEntry) return UpdateLocalVariableTypes(); - if (PinBase) return UpdateEditablePinBaseTypes(); -} - -void WingVariables::UpdateVariableFlags() -{ - if (Blueprint) return UpdateBlueprintVariableFlags(); - if (FuncEntry) return UpdateLocalVariableFlags(); - if (PinBase) return UpdateEditablePinBaseFlags(); -} - -bool WingVariables::UpdateVariableDefaults() -{ - if (Blueprint) return UpdateBlueprintVariableDefaults(); - if (FuncEntry) return UpdateLocalVariableDefaults(); - if (PinBase) return UpdateEditablePinBaseDefaults(); - return true; -} - -bool WingVariables::CheckEmpty(Cat Category) -{ - if (!Variables.IsEmpty()) - { - UWingServer::Printf(TEXT("This kind of graph is not allowed to have %s"), - *StaticEnum()->GetNameStringByValue(int(Category))); - return false; - } - return true; -} - -bool WingVariables::CheckSanity(Cat Category) -{ - const TSet &Relevant = GetRelevantFlagSet(Category); for (const Var &Variable : Variables) { FString VarName = WingUtils::ExternalizeID(Variable.Name); @@ -229,10 +91,10 @@ bool WingVariables::CheckSanity(Cat Category) } for (FName Flag : Variable.Flags) { - if (!Relevant.Contains(Flag)) + if (!GoodFlags.Contains(Flag)) { UWingServer::Printf(TEXT("Flag %s is not valid here. Valid flags are:"), *Flag.ToString()); - for (FName Rel : Relevant) UWingServer::Printf(TEXT(" %s\n"), *Rel.ToString()); + for (FName Rel : GoodFlags) UWingServer::Printf(TEXT(" %s\n"), *Rel.ToString()); return false; } } @@ -240,28 +102,51 @@ bool WingVariables::CheckSanity(Cat Category) return true; } -bool WingVariables::ParseVariableFlags(WingTokenizer &Tok, TSet &Out) +bool WingVariableList::ParseString(const FString &Input) { - Tok.Advance(); // Step over open-paren - while (Tok.TokenIs(Tok.Identifier)) + Variables.Empty(); + + TArray Lines; + Input.ParseIntoArrayLines(Lines); + + for (const FString& Line : Lines) { - Out.Add(Tok.NextName()); - Tok.Advance(); - // Commas are optional. - if (Tok.TokenIs(',')) Tok.Advance(); + WingTokenizer Tok(Line); + if (Tok.NextType() == 0) continue; + Var V; + V.DefaultSpecified = false; + if (!ParseOneVariable(Tok, V)) return false; + Variables.Add(MoveTemp(V)); } - 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; } -bool WingVariables::ParseOneVariable(WingTokenizer &Tok, Var &V) +bool WingVariableList::ParseNamesString(const FString &Input) +{ + Variables.Empty(); + WingTokenizer Tok(Input); + while (Tok.TokenIs(Tok.Identifier)) + { + FName Name = Tok.NextName(); + Var V; + V.Name = Name; + Variables.Add(V); + V.DefaultSpecified = false; + Tok.Advance(); + if (Tok.TokenIs(',')) Tok.Advance(); + } + if (!Tok.TokenIs(0)) + { + Tok.SaveCursor(NAME_None); + UWingServer::Printf(TEXT("Unexpected token %s in variable list"), + *FString(Tok.GetRange(NAME_None, 1))); + return false; + } + return true; +} + +bool WingVariableList::ParseOneVariable(WingTokenizer &Tok, Var &V) { // Parse type. UWingTypes::Requirements Req; @@ -306,287 +191,387 @@ bool WingVariables::ParseOneVariable(WingTokenizer &Tok, Var &V) return true; } -bool WingVariables::ParseString(const FString &Input) +bool WingVariableList::ParseVariableFlags(WingTokenizer &Tok, TSet &Out) { - Variables.Empty(); - - TArray Lines; - Input.ParseIntoArrayLines(Lines); - - for (const FString& Line : Lines) + Tok.Advance(); // Step over open-paren + while (Tok.TokenIs(Tok.Identifier)) { - WingTokenizer Tok(Line); - if (Tok.NextType() == 0) continue; - Var V; - V.DefaultSpecified = false; - if (!ParseOneVariable(Tok, V)) return false; - Variables.Add(MoveTemp(V)); + Out.Add(Tok.NextName()); + Tok.Advance(); + // Commas are optional. + if (Tok.TokenIs(',')) Tok.Advance(); } - + 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::Var WingVariables::ParseVariableDescription(const FBPVariableDescription &Desc, WingVariables::Cat Category) +void WingVariables::Empty() +{ + BlueprintVariables.Empty(); + LocalVariables.Empty(); + InputVariables.Empty(); + OutputVariables.Empty(); +} + +void WingVariables::ClearLinks() +{ + BlueprintVariables.ClearLinks(); + LocalVariables.ClearLinks(); + InputVariables.ClearLinks(); + OutputVariables.ClearLinks(); +} + +void WingVariables::Print(FStringBuilderBase &Out) +{ + BlueprintVariables.Print(Out); + LocalVariables.Print(Out); + InputVariables.Print(Out); + OutputVariables.Print(Out); +} + +void WingVariables::Load() +{ + Empty(); + if (Blueprint != nullptr) return LoadBlueprint(); + if (Graph != nullptr) return LoadGraph(); +} + +void WingVariables::LoadBlueprint() +{ + UObject *CDO = WingUtils::GetGeneratedCDO(Blueprint); + + for (FBPVariableDescription& Desc : Blueprint->NewVariables) + { + // Skip event dispatchers. + if (Desc.VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate) continue; + // Load up the variable. + Var V = LoadBlueprintVariableDescription(Desc, CDO); + BlueprintVariables.Variables.Add(MoveTemp(V)); + } +} + +void WingVariables::LoadGraph() +{ + TWeakObjectPtr EntryNode; + TWeakObjectPtr ResultNode; + FBlueprintEditorUtils::GetEntryAndResultNodes(Graph, EntryNode, ResultNode); + if (EntryNode.IsValid()) LoadEditablePinBase(EntryNode.Get(), InputVariables); + if (ResultNode.IsValid()) LoadEditablePinBase(ResultNode.Get(), OutputVariables); + LoadLocalVariables(EntryNode.Get()); +} + +void WingVariables::LoadLocalVariables(UK2Node_EditablePinBase *Node) +{ + UK2Node_FunctionEntry *Func = Cast(Node); + if (Func == nullptr) return; + for (FBPVariableDescription& Desc : Func->LocalVariables) + { + Var V = LoadLocalVariableDescription(Desc); + LocalVariables.Variables.Add(MoveTemp(V)); + } +} + +WingVariables::Var WingVariables::LoadBlueprintVariableDescription(FBPVariableDescription &Desc, UObject *CDO) { Var Result; Result.Name = Desc.VarName; Result.Type = Desc.VarType; if (!(Desc.PropertyFlags & CPF_DisableEditOnInstance)) - Result.AddFlagIfRelevant(Flag_InstanceEditable, Category); + Result.Flags.Add(Flag_InstanceEditable); if (Desc.PropertyFlags & CPF_BlueprintReadOnly) - Result.AddFlagIfRelevant(Flag_BlueprintReadOnly, Category); + Result.Flags.Add(Flag_BlueprintReadOnly); if (Desc.PropertyFlags & CPF_Interp) - Result.AddFlagIfRelevant(Flag_ExposeToCinematics, Category); + Result.Flags.Add(Flag_ExposeToCinematics); if (Desc.HasMetaData(FBlueprintMetadata::MD_ExposeOnSpawn)) - Result.AddFlagIfRelevant(Flag_ExposeOnSpawn, Category); + Result.Flags.Add(Flag_ExposeOnSpawn); if (Desc.HasMetaData(FBlueprintMetadata::MD_Private)) - Result.AddFlagIfRelevant(Flag_Private, Category); + Result.Flags.Add(Flag_Private); + + // Read default value from CDO if available. + if (CDO) + { + FProperty* Prop = CDO->GetClass()->FindPropertyByName(Desc.VarName); + if (Prop) + { + Result.DefaultValue = FWingProperty(Prop, CDO).GetText(); + Result.DefaultSpecified = true; + } + } return Result; } -void WingVariables::PrintAll(FStringBuilderBase &Out, bool Always, const TCHAR *Header) +WingVariables::Var WingVariables::LoadLocalVariableDescription(FBPVariableDescription &Desc) { - if (Header != nullptr) - { - if (Always || (!Variables.IsEmpty())) - { - Out.Append(Header); - Out.AppendChar('\n'); - } - } - for (const Var& V : Variables) - { - FString TypeStr = UWingTypes::TypeToText(V.Type); - FString NameStr = WingUtils::ExternalizeID(V.Name); + Var Result; + Result.Name = Desc.VarName; + Result.Type = Desc.VarType; + Result.DefaultValue = Desc.DefaultValue; + Result.DefaultSpecified = true; + return Result; +} - // Build flags string. - FString FlagsStr; - if (V.Flags.Num() > 0) - { - TArray Sorted = V.Flags.Array(); - Sorted.Sort(FNameLexicalLess()); - for (const FName& Flag : Sorted) - { - if (!FlagsStr.IsEmpty()) FlagsStr += TEXT(", "); - FlagsStr += Flag.ToString(); - } - } - - // Print: type name (flags) = defaultvalue - Out.Appendf(TEXT(" %s %s"), *TypeStr, *NameStr); - if (!FlagsStr.IsEmpty()) - Out.Appendf(TEXT(" (%s)"), *FlagsStr); - if (!V.DefaultValue.IsEmpty()) - Out.Appendf(TEXT(" = %s"), *V.DefaultValue); - Out.Append(TEXT("\n")); +void WingVariables::LoadEditablePinBase(UK2Node_EditablePinBase* Node, WingVariableList &List) +{ + if (Node == nullptr) return; + for (const TSharedPtr& PinInfo : Node->UserDefinedPins) + { + Var V; + V.Name = PinInfo->PinName; + V.Type = PinInfo->PinType; + V.DefaultValue = PinInfo->PinDefaultValue; + V.DefaultSpecified = true; + List.Variables.Add(MoveTemp(V)); } } -void WingVariables::UpdateBlueprintVariableTypes() +bool WingVariables::Check() { - for (const Var &Input : Variables) - { - Input.BPVar->VarType = Input.Type; - } + if (Blueprint) return CheckBlueprint(); + if (Graph) return CheckGraph(); + check(false); + return false; } -void WingVariables::UpdateBlueprintVariableFlags() +bool WingVariables::CheckBlueprint() { - 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 OK = true; + if (!BlueprintVariables.CheckSanity(Flags_BlueprintVariables, true)) OK = false; + if (!LocalVariables.CheckSanity(Flags_None, false)) OK = false; + if (!InputVariables.CheckSanity(Flags_None, false)) OK = false; + if (!OutputVariables.CheckSanity(Flags_None, false)) OK = false; + return OK; } -bool WingVariables::UpdateBlueprintVariableDefaults() +bool WingVariables::CheckGraph() { - UObject *CDO = nullptr; - if (Blueprint->GeneratedClass) CDO = Blueprint->GeneratedClass->GetDefaultObject(); - if (CDO == nullptr) + EGraphType T = Graph->GetSchema()->GetGraphType(Graph); + bool AllowLocal = (T == EGraphType::GT_Function); + bool AllowInput = (T == EGraphType::GT_Function) || (T == EGraphType::GT_Macro); + bool AllowOutput = (T == EGraphType::GT_Function) || (T == EGraphType::GT_Macro); + if ((!AllowLocal) && (!AllowInput) && (!AllowOutput)) { - UWingServer::Printf(TEXT("Blueprint is not compiled, cannot update variable defaults.")); + UWingServer::Printf(TEXT("This graph has no editable variables.")); return false; } - int OK = true; - for (const Var &Input : Variables) + + bool OK = true; + if (!BlueprintVariables.CheckSanity(Flags_None, false)) OK = false; + if (!LocalVariables.CheckSanity(Flags_None, AllowLocal)) OK = false; + if (!InputVariables.CheckSanity(Flags_None, AllowInput)) OK = false; + if (!OutputVariables.CheckSanity(Flags_None, AllowOutput)) OK = false; + return OK; +} + +bool WingVariables::Modify() +{ + if (Blueprint) return ModifyBlueprint(); + if (Graph) return ModifyGraph(); + check(false); + return false; +} + +bool WingVariables::ModifyBlueprint() +{ + if (!CheckBlueprint()) return false; + ClearLinks(); + + // Link our variables to blueprint variable descriptions. + for (Var &V : BlueprintVariables.Variables) + { + V.BPVar = WingUtils::FindOneWithInternalID(V.Name, Blueprint->NewVariables, TEXT("non-inherited variable")); + if (V.BPVar == nullptr) return false; + } + + // Update the type and the flags. + bool AnyDefaults = false; + for (Var &V : BlueprintVariables.Variables) + { + V.BPVar->VarType = V.Type; + ModifyBlueprintVariableFlags(V); + if (V.DefaultSpecified) AnyDefaults = true; + } + + // Next step not needed if no defaults specified. + if (!AnyDefaults) return true; + + FKismetEditorUtilities::CompileBlueprint(Blueprint); + + UObject *CDO = WingUtils::GetGeneratedCDO(Blueprint); + if (!CDO) + { + UWingServer::Printf(TEXT("Cannot store variable defaults, blueprint didn't compile")); + return false; + } + if (!ModifyDefaultsAsProperties(BlueprintVariables, CDO)) return false; + + return true; +} + +void WingVariables::ModifyBlueprintVariableFlags(Var &Input) +{ + 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::ModifyDefaultsAsProperties(WingVariableList &List, UObject *CDO) +{ + for (Var &Input : List.Variables) { if (Input.DefaultSpecified) { - FProperty* Prop = Blueprint->GeneratedClass->FindPropertyByName(Input.Name); + FProperty* Prop = CDO->GetClass()->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; + if (!FWingProperty(Prop, CDO).SetText(Input.DefaultValue)) return false; } } - return OK; + return true; } -void WingVariables::UpdateLocalVariableTypes() +bool WingVariables::ModifyGraph() { - for (const Var &Input : Variables) + if (!CheckGraph()) return false; + ClearLinks(); + + UK2Node_EditablePinBase *InputNode, *OutputNode; + UK2Node_FunctionEntry *LocalNode; + if (!GetGraphNodes(InputNode, OutputNode, LocalNode)) return false; + + for (Var &V : LocalVariables.Variables) { - Input.BPVar->VarType = Input.Type; + FBPVariableDescription *Desc = + WingUtils::FindOneWithInternalID(V.Name, LocalNode->LocalVariables, TEXT("local variable")); + if (Desc == nullptr) return false; + Desc->VarType = V.Type; + if (V.DefaultSpecified) Desc->DefaultValue = V.DefaultValue; } -} - -void WingVariables::UpdateLocalVariableFlags() -{ - for (const Var &Input : Variables) + for (Var &V : InputVariables.Variables) { - FBPVariableDescription &Out = *Input.BPVar; - // Currently, no supported flags for local variables. + TSharedPtr *Found = + WingUtils::FindOneWithInternalID(V.Name, InputNode->UserDefinedPins, TEXT("input variable")); + if (!Found) return false; + (*Found)->PinType = V.Type; + if (V.DefaultSpecified) (*Found)->PinDefaultValue = V.DefaultValue; } + for (Var &V : OutputVariables.Variables) + { + TSharedPtr *Found = + WingUtils::FindOneWithInternalID(V.Name, OutputNode->UserDefinedPins, TEXT("output variable")); + if (!Found) return false; + (*Found)->PinType = V.Type; + if (V.DefaultSpecified) (*Found)->PinDefaultValue = V.DefaultValue; + } + + if (InputNode) InputNode->ReconstructNode(); + if (OutputNode) OutputNode->ReconstructNode(); + return true; } -bool WingVariables::UpdateLocalVariableDefaults() +bool WingVariables::GetGraphNodes( + UK2Node_EditablePinBase *&InputNode, + UK2Node_EditablePinBase *&OutputNode, + UK2Node_FunctionEntry *&LocalNode) { - for (const Var &Input : Variables) + // In theory, none of these errors should trigger, because + // we call CheckSanity before calling this function. But + // you never know. + InputNode = nullptr; + OutputNode = nullptr; + LocalNode = nullptr; + + TWeakObjectPtr Inputs, Outputs; + FBlueprintEditorUtils::GetEntryAndResultNodes(Graph, Inputs, Outputs); + if (!Inputs.IsValid()) { - if (Input.DefaultSpecified) + UWingServer::Printf(TEXT("ERROR: no function entry node for graph.")); + return false; + } + if (!Outputs.IsValid() && (!OutputVariables.Variables.IsEmpty())) + { + Outputs = FBlueprintEditorUtils::FindOrCreateFunctionResultNode(Inputs.Get()); + if (!Outputs.IsValid()) { - Input.BPVar->DefaultValue = Input.DefaultValue; + UWingServer::Printf(TEXT("ERROR: couldn't create result node for graph.")); + return false; } } - 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) + UK2Node_FunctionEntry *Locals = Cast(Inputs.Get()); + if (!Locals && (!LocalVariables.Variables.IsEmpty())) { - if (Input.DefaultSpecified) - Input.Pin->PinDefaultValue = Input.DefaultValue; - } - PinBase->ReconstructNode(); - return true; -} - -void WingGraphVariables::LoadGraph(const UEdGraph *Graph) -{ - TWeakObjectPtr EntryNode; - TWeakObjectPtr ResultNode; - FBlueprintEditorUtils::GetEntryAndResultNodes(Graph, EntryNode, ResultNode); - Arguments.LoadEditablePinBase(EntryNode.Get()); - ReturnValues.LoadEditablePinBase(ResultNode.Get()); - LocalVariables.LoadLocalVariables(EntryNode.Get()); -} - -bool WingGraphVariables::ParseStrings(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::AssociateGraph(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; -} - -void WingGraphVariables::PrintAll(FStringBuilderBase &Out, bool Always) -{ - Arguments.PrintAll(Out, Always, TEXT("Arguments:")); - ReturnValues.PrintAll(Out, Always, TEXT("ReturnValues:")); - LocalVariables.PrintAll(Out, Always, TEXT("LocalVariables:")); -} - -void WingGraphVariables::UpdateVariableTypes() -{ - Arguments.UpdateVariableTypes(); - ReturnValues.UpdateVariableTypes(); - LocalVariables.UpdateVariableTypes(); -} - -void WingGraphVariables::UpdateVariableFlags() -{ - Arguments.UpdateVariableFlags(); - ReturnValues.UpdateVariableFlags(); - LocalVariables.UpdateVariableFlags(); -} - -bool WingGraphVariables::UpdateVariableDefaults() -{ - return Arguments.UpdateVariableDefaults() && - ReturnValues.UpdateVariableDefaults() && - LocalVariables.UpdateVariableDefaults(); -} - -bool WingGraphVariables::CheckSanity(const UEdGraph *Graph) -{ - EGraphType Type = Graph->GetSchema()->GetGraphType(Graph); - if (Type == EGraphType::GT_Function) - { - return Arguments.CheckSanity(Cat::FunctionArguments) && - ReturnValues.CheckSanity(Cat::FunctionReturnValues) && - LocalVariables.CheckSanity(Cat::LocalVariables); - } - else if (Type == EGraphType::GT_Macro) - { - return Arguments.CheckSanity(Cat::MacroEntry) && - ReturnValues.CheckSanity(Cat::MacroExit) && - LocalVariables.CheckEmpty(Cat::LocalVariables); - } - else - { - UWingServer::Printf( - TEXT("Graphs of this type may not have arguments, return values, or local variables.")); + UWingServer::Printf(TEXT("ERROR: function doesn't have a proper entry node for local variables")); return false; } + InputNode = Inputs.Get(); + OutputNode = Outputs.Get(); + LocalNode = Locals; + return true; } + // // Create the variables. + // for (const WingVariables::Var& V : Vars.GetVariables()) + // { + // if (!FBlueprintEditorUtils::AddMemberVariable(BP, V.Name, V.Type)) + // { + // UWingServer::Printf(TEXT("ERROR: Failed to add variable '%s'\n"), + // *WingUtils::ExternalizeID(V.Name)); + // return; + // } + // } + + // // Check for name collisions against existing variables, components, and the like. + // TSet Names; + // FBlueprintEditorUtils::GetClassVariableList(BP, Names); + // if (!WingUtils::FindNoDuplicateNames(Names, Vars.GetVariables(), TEXT("variable or component"))) return; + + + // // Check for duplicate names against existing pins and locals. + // TSet Names; + // WingUtils::FindNoDuplicateNames(Names, EntryNode->UserDefinedPins, TEXT("local variables")); + // WingUtils::FindNoDuplicateNames(Names, ResultNode->UserDefinedPins, TEXT("local variables")); + // if (UK2Node_FunctionEntry* FuncEntry = Cast(EntryNode.Get())) + // WingUtils::FindNoDuplicateNames(Names, FuncEntry->LocalVariables, TEXT("local variables")); + // if (!WingUtils::FindNoDuplicateNames(Names, Vars.Arguments.GetVariables(), TEXT("local variables"))) return; + // if (!WingUtils::FindNoDuplicateNames(Names, Vars.ReturnValues.GetVariables(), TEXT("local variables"))) return; + // if (!WingUtils::FindNoDuplicateNames(Names, Vars.LocalVariables.GetVariables(), TEXT("local variables"))) return; diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h index 5415758c..7c2eeb4f 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h @@ -240,7 +240,8 @@ public: static TArray AllNodes(UBlueprint* BP); static TArray AllNodes(UEdGraph *Graph); static TArray GetAncestorBlueprints(UBlueprint *BP, bool OldestFirst = false); - + static UObject *GetGeneratedCDO(UBlueprint *BP); + // ----- Material helpers ----- static void EnsureMaterialGraph(UMaterial* Material); diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingVariables.h b/Plugins/UEWingman/Source/UEWingman/Public/WingVariables.h index 3ab1cc03..a4078cdd 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingVariables.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingVariables.h @@ -7,23 +7,12 @@ struct WingTokenizer; class UK2Node_EditablePinBase; class UK2Node_FunctionEntry; +struct FUserPinInfo; -UENUM() -enum class EWingVariableCategory : uint8 -{ - BlueprintVariables, - LocalVariables, - FunctionArguments, - FunctionReturnValues, - MacroEntry, - MacroExit -}; -class WingVariables +class WingVariableList { public: - using Cat = EWingVariableCategory; - struct Var { // Internal name. @@ -36,7 +25,7 @@ public: FString DefaultValue; // When parsing a string, true if default was specified. - bool DefaultSpecified = true; + bool DefaultSpecified = false; // Boolean flags. TSet Flags; @@ -48,109 +37,115 @@ public: // 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: - // A list of all the variables. +public: + // The actual list of variables. TArray Variables; - // A pointer to a blueprint. - UBlueprint *Blueprint = nullptr; + // The list has a name, which is used for generating good messages. + const TCHAR *ListName; - // A pointer to a Function entry node. - UK2Node_FunctionEntry *FuncEntry = nullptr; + // Constructor. + WingVariableList(const TCHAR *MyListName) : ListName(MyListName) {} - // 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); + // Return true if the variables are empty. + void Empty() { Variables.Empty(); } - // Check if the variable list is empty. - bool IsEmpty() const { return Variables.IsEmpty(); } - - // Print all the variables to a string builder. - void PrintAll(FStringBuilderBase &Out, bool Always, const TCHAR *Header); + // Print the variables to a string builder. + void Print(FStringBuilderBase &Out); - // Clear everything - void Clear(); + // Clear the BPVar and Pin fields. + void ClearLinks(); - // Clear any association. - void ClearAssociation(); + // Check the sanity of the vars in the array. If allow + // is false, then no variables are allowed in the array. + bool CheckSanity(const TSet &GoodFlags, bool Allow); - // Check the sanity. - bool CheckSanity(Cat C); - - // Check that the variables are empty. - bool CheckEmpty(Cat C); - - // Parse variables from a string. One variable per line. - // Format: type name (flag1, flag2) = defaultvalue - // Returns false on parse error. + // Parse variables from a string. bool ParseString(const FString &Input); - // Make a best effort to load all available existing state. - // This does not associate the variables. If you want to manipulate - // the variables, you'll have to associate them. - void LoadBlueprintVariables(UBlueprint *BP); - void LoadLocalVariables(UK2Node_EditablePinBase* Node); - void LoadEditablePinBase(UK2Node_EditablePinBase* Node); - - // Associate every variable in the list with an existing - // variable in a blueprint or graph. - bool AssociateBlueprintVariables(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(); + // Parse variable names only from a string. + bool ParseNamesString(const FString &Input); 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(); + bool ParseOneVariable(WingTokenizer &Tok, Var &V); + bool ParseVariableFlags(WingTokenizer &Tok, TSet &Out); }; -struct WingGraphVariables +class WingVariables { - WingVariables Arguments; - WingVariables ReturnValues; - WingVariables LocalVariables; +public: + using Var = WingVariableList::Var; - WingGraphVariables() {} - - using Cat = WingVariables::Cat; + // The backing store. Only one of these should be set. - void LoadGraph(const UEdGraph *Graph); - bool AssociateGraph(const UEdGraph *Graph); - bool ParseStrings(const FString &A, const FString &R, const FString &L); - void PrintAll(FStringBuilderBase &Out, bool Always); - void UpdateVariableTypes(); - void UpdateVariableFlags(); - bool UpdateVariableDefaults(); - bool CheckSanity(const UEdGraph *Graph); + UBlueprint *Blueprint = nullptr; + UEdGraph *Graph = nullptr; + + // The Workspace. At any given time, these may or may not contain + // the same data as the backing store. + + WingVariableList BlueprintVariables{TEXT("Blueprint Variables")}; + WingVariableList LocalVariables{TEXT("Local Variables")}; + WingVariableList InputVariables{TEXT("Input Variables")}; + WingVariableList OutputVariables{TEXT("Output Variables")}; + + // Constructors. Just initialize the pointers to the backing store. + + WingVariables(UBlueprint *BP) : Blueprint(BP) {} + WingVariables(UEdGraph *G) : Graph(G) {} + + // Clear the workspace. Doesn't affect the backing store. + + void Empty(); + + // Clear the BPVar and Pin fields. + + void ClearLinks(); + + // Print the contents of the workspace. + + void Print(FStringBuilderBase &Out); + + // Load: clear the workspace, then + // copy everything from the backing store into the workspace. + + void Load(); + + // Check: make sure the contents of the workspace makes sense + // given the type of backing store. + + bool Check(); + + // Use the variables in the workspace to modify the backing store. + + bool Modify(); + + // Create every variable in the workspace in the backing store. + + bool Create() { return true; } private: - static Cat GraphEntryCategory(const UEdGraph *Graph); - static Cat GraphExitCategory(const UEdGraph *Graph); + void LoadBlueprint(); + void LoadGraph(); + void LoadLocalVariables(UK2Node_EditablePinBase *Node); + Var LoadBlueprintVariableDescription(FBPVariableDescription &Desc, UObject *CDO); + Var LoadLocalVariableDescription(FBPVariableDescription &Desc); + void LoadEditablePinBase(UK2Node_EditablePinBase *Node, WingVariableList &List); + + bool CheckBlueprint(); + bool CheckGraph(); + + bool ModifyBlueprint(); + void ModifyBlueprintVariableFlags(Var &Input); + bool ModifyDefaultsAsProperties(WingVariableList &List, UObject *CDO); + + bool ModifyGraph(); + + bool GetGraphNodes( + UK2Node_EditablePinBase *&InputNode, + UK2Node_EditablePinBase *&OutputNode, + UK2Node_FunctionEntry *&LocalNode); };