Code is all tore up

This commit is contained in:
2026-03-30 20:10:23 -04:00
parent 386455b1d0
commit e982fec1a4
19 changed files with 876 additions and 179 deletions

Binary file not shown.

View File

@@ -11,6 +11,7 @@
#include "Engine/SimpleConstructionScript.h" #include "Engine/SimpleConstructionScript.h"
#include "Engine/SCS_Node.h" #include "Engine/SCS_Node.h"
#include "Components/ActorComponent.h" #include "Components/ActorComponent.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "ActorComponent_Add.generated.h" #include "ActorComponent_Add.generated.h"
@@ -51,8 +52,9 @@ public:
// Check that the proposed name is valid // Check that the proposed name is valid
FName InternalID = WingUtils::CheckProposedName(Component); FName InternalID = WingUtils::CheckProposedName(Component);
if (InternalID.IsNone()) return; if (InternalID.IsNone()) return;
TArray<UWingComponentReference*> AllComponents = UWingComponentReference::GetAll(BP); TSet<FName> Names;
if (!WingUtils::FindNoneWithInternalID(InternalID, AllComponents, TEXT("Component"))) return; FBlueprintEditorUtils::GetClassVariableList(BP, Names);
if (!WingUtils::FindNoDuplicateName(Names, InternalID, TEXT("variable or component"))) return;
// Resolve the component class by name // Resolve the component class by name
UWingTypes::Requirements Req; UWingTypes::Requirements Req;
@@ -65,6 +67,7 @@ public:
if (!UWingComponentReference::CheckValidComponentClass(ComponentClass)) return; if (!UWingComponentReference::CheckValidComponentClass(ComponentClass)) return;
// Find the specified parent component // Find the specified parent component
TArray<UWingComponentReference*> AllComponents = UWingComponentReference::GetAll(BP);
UWingComponentReference* ParentComp = WingUtils::FindOneWithExternalID(Parent, AllComponents, TEXT("Component")); UWingComponentReference* ParentComp = WingUtils::FindOneWithExternalID(Parent, AllComponents, TEXT("Component"));
if (!ParentComp) return; if (!ParentComp) return;

View File

@@ -4,13 +4,12 @@
#include "WingServer.h" #include "WingServer.h"
#include "WingHandler.h" #include "WingHandler.h"
#include "WingFetcher.h" #include "WingFetcher.h"
#include "WingProperty.h"
#include "WingBlueprintVar.h"
#include "WingUtils.h" #include "WingUtils.h"
#include "WingTypes.h" #include "WingTypes.h"
#include "WingVariables.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "EdGraphSchema_K2.h"
#include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "BlueprintVariable_Create.generated.h" #include "BlueprintVariable_Create.generated.h"
@@ -27,15 +26,12 @@ public:
UPROPERTY(meta=(Description="Blueprint name or package path")) UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint; FString Blueprint;
UPROPERTY(meta=(Description="Name of the new variable")) UPROPERTY(meta=(Description="Variable declarations, one per line. Format: type name (flags) = default"))
FString Name; FString Variables;
UPROPERTY(meta=(Optional, Description="Variable configuration: VarType, Category, DefaultValue, InstanceEditable, BlueprintReadOnly, ExposeOnSpawn, Private, ExposeToCinematics, etc."))
FWingJsonObject Config;
virtual FString GetDescription() const override 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 virtual void Handle() override
@@ -44,32 +40,33 @@ public:
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;
// Check validity of the proposed name // Parse the variable declarations.
FName InternalID = WingUtils::CheckProposedName(Name); WingVariables Parsed;
if (InternalID.IsNone()) return; if (!WingVariables::ParseString(Variables, Parsed)) return;
if (!WingUtils::FindNoneWithInternalID(InternalID, BP->NewVariables, TEXT("Variable"))) 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 // Check for name collisions against existing variables, components, and the like.
FEdGraphPinType DefaultType; TSet<FName> Names;
DefaultType.PinCategory = UEdGraphSchema_K2::PC_Int; FBlueprintEditorUtils::GetClassVariableList(BP, Names);
if (!FBlueprintEditorUtils::AddMemberVariable(BP, InternalID, DefaultType)) 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)); if (!FBlueprintEditorUtils::AddMemberVariable(BP, V.Name, V.Type))
{
UWingServer::Printf(TEXT("ERROR: Failed to add variable '%s'\n"),
*WingUtils::ExternalizeID(V.Name));
return; 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()))
return;
} }
UWingServer::Printf(TEXT("Created variable %s (%s) in %s\n"), // Update everything.
*Name, *UWingTypes::TypeToText(Editor.Desc->VarType), *WingUtils::FormatName(BP)); if (!Parsed.AssociateBlueprintNewVariables(BP)) return;
Parsed.UpdateVariableFlags();
FKismetEditorUtilities::CompileBlueprint(BP);
if (!Parsed.UpdateVariableDefaults()) return;
UWingServer::Printf(TEXT("Success.\n"));
} }
}; };

View File

@@ -4,10 +4,7 @@
#include "WingServer.h" #include "WingServer.h"
#include "WingHandler.h" #include "WingHandler.h"
#include "WingFetcher.h" #include "WingFetcher.h"
#include "WingUtils.h" #include "WingVariables.h"
#include "WingBlueprintVar.h"
#include "Engine/Blueprint.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "BlueprintVariable_Dump.generated.h" #include "BlueprintVariable_Dump.generated.h"
@@ -24,12 +21,9 @@ public:
UPROPERTY(meta=(Description="Blueprint name or package path")) UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint; FString Blueprint;
UPROPERTY(meta=(Description="Name of the variable to inspect"))
FString Variable;
virtual FString GetDescription() const override 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 virtual void Handle() override
@@ -38,10 +32,8 @@ public:
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;
FWingBlueprintVar Editor(BP, Variable); WingVariables Vars = WingVariables::ParseBlueprintVariables(BP);
if (Editor.NotFound()) return; if (Vars.IsEmpty()) { UWingServer::Print(TEXT("No variables.\n")); return; }
Vars.PrintAll();
UWingServer::Printf(TEXT("Variable %s in %s:\n"), *Variable, *WingUtils::FormatName(BP));
Editor.Dump();
} }
}; };

View File

@@ -4,12 +4,10 @@
#include "WingServer.h" #include "WingServer.h"
#include "WingHandler.h" #include "WingHandler.h"
#include "WingFetcher.h" #include "WingFetcher.h"
#include "WingProperty.h"
#include "WingBlueprintVar.h"
#include "WingUtils.h" #include "WingUtils.h"
#include "WingTypes.h" #include "WingVariables.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h"
#include "BlueprintVariable_Modify.generated.h" #include "BlueprintVariable_Modify.generated.h"
@@ -26,15 +24,12 @@ public:
UPROPERTY(meta=(Description="Blueprint name or package path")) UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint; FString Blueprint;
UPROPERTY(meta=(Description="Name of the variable to modify")) UPROPERTY(meta=(Description="Variable declarations, one per line. Format: type name (flags) = default"))
FString Variable; FString Variables;
UPROPERTY(meta=(Description="Properties to change: VarType, Category, DefaultValue, InstanceEditable, BlueprintReadOnly, ExposeOnSpawn, Private, ExposeToCinematics, etc."))
FWingJsonObject Properties;
virtual FString GetDescription() const override 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 virtual void Handle() override
@@ -43,19 +38,21 @@ public:
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;
FWingBlueprintVar Editor(BP, Variable); // Parse the variable declarations.
if (Editor.NotFound()) 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::Blueprint)) return;
if (!Properties.Json || Properties.Json->Values.Num() == 0) // Associate with existing blueprint variables.
{ if (!Parsed.AssociateBlueprintNewVariables(BP)) return;
UWingServer::Print(TEXT("ERROR: No properties specified\n"));
return;
}
if (!Editor.ApplyJson(Properties.Json.Get())) // Update types and flags, compile, then update defaults.
return; Parsed.UpdateVariableTypes();
Parsed.UpdateVariableFlags();
FKismetEditorUtilities::CompileBlueprint(BP);
if (!Parsed.UpdateVariableDefaults()) return;
UWingServer::Printf(TEXT("Modified variable %s (%s) in %s\n"), UWingServer::Printf(TEXT("Success.\n"));
*Variable, *UWingTypes::TypeToText(Editor.Desc->VarType), *WingUtils::FormatName(BP));
} }
}; };

View File

@@ -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<UEdGraph>();
if (!G) return;
FStringBuilderBase &Output = UWingServer::GetPrintBuffer();
TWeakObjectPtr<UK2Node_EditablePinBase> EntryNode;
TWeakObjectPtr<UK2Node_EditablePinBase> 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);
}
}
};

View File

@@ -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<UEdGraph>();
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"));
}
};

View File

@@ -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);
}
};

View File

@@ -9,6 +9,7 @@
#include "WidgetBlueprint.h" #include "WidgetBlueprint.h"
#include "Blueprint/WidgetTree.h" #include "Blueprint/WidgetTree.h"
#include "Blueprint/UserWidget.h" #include "Blueprint/UserWidget.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Components/PanelWidget.h" #include "Components/PanelWidget.h"
#include "Widget_Create.generated.h" #include "Widget_Create.generated.h"
@@ -72,7 +73,10 @@ public:
// Check that the name is unique among existing widgets. // Check that the name is unique among existing widgets.
TArray<UWidget*> AllWidgets; TArray<UWidget*> AllWidgets;
Tree->GetAllWidgets(AllWidgets); Tree->GetAllWidgets(AllWidgets);
if (!WingUtils::FindNoneWithInternalID(InternalID, AllWidgets, TEXT("Widget"))) return; TSet<FName> 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. // If a parent is specified, find it and verify it's a panel.
UPanelWidget* ParentPanel = nullptr; UPanelWidget* ParentPanel = nullptr;

View File

@@ -141,6 +141,14 @@ void UWingComponentReference::AddChildNode(UBlueprint *BP, USCS_Node *NewNode, F
bool UWingComponentReference::AddComponent(UBlueprint *BP, UClass *Class, UWingComponentReference *Parent, FName Name) bool UWingComponentReference::AddComponent(UBlueprint *BP, UClass *Class, UWingComponentReference *Parent, FName Name)
{ {
TSet<FName> 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); FoundComponent ExistingComponent = FindComponent(BP, Name);
if (!CheckNoSuchComponent(ExistingComponent)) return false; if (!CheckNoSuchComponent(ExistingComponent)) return false;
FoundComponent ParentComponent = FindComponent(BP, Parent->VariableName); FoundComponent ParentComponent = FindComponent(BP, Parent->VariableName);

View File

@@ -13,6 +13,7 @@
#include "WingFunctionArgs.h" #include "WingFunctionArgs.h"
#include "WingVariables.h" #include "WingVariables.h"
#include "MaterialGraph/MaterialGraphNode.h" #include "MaterialGraph/MaterialGraphNode.h"
#include "Kismet2/BlueprintEditorUtils.h"
WingGraphExport::WingGraphExport(UEdGraph* InGraph) WingGraphExport::WingGraphExport(UEdGraph* InGraph)
: Graph(InGraph) : Graph(InGraph)
@@ -288,18 +289,26 @@ void WingGraphExport::EmitMaterialProperties(UEdGraphNode* Node, FStringBuilderB
void WingGraphExport::EmitLocalVariables() void WingGraphExport::EmitLocalVariables()
{ {
for (UEdGraphNode* Node : Graph->Nodes) TWeakObjectPtr<UK2Node_EditablePinBase> EntryNode;
TWeakObjectPtr<UK2Node_EditablePinBase> 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<UK2Node_FunctionEntry>(Node); Output.Appendf(TEXT("Arguments:\n"));
if (!EntryNode) continue; Arguments.PrintAll(Output, 4);
WingVariables Locals = WingVariables::ParseFunctionLocalVariables(EntryNode);
if (!Locals.IsEmpty())
{
Output.Appendf(TEXT("Function Local Variables:\n"));
Locals.PrintAll(Output, 4);
} }
break; 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);
} }
} }

View File

@@ -44,19 +44,21 @@ WingCharacterClasses::WingCharacterClasses()
WingCharacterClasses WingCharacterClasses::TheSet; WingCharacterClasses WingCharacterClasses::TheSet;
void WingTokenizer::Add(TCHAR Type, FName Name) void WingTokenizer::Add(TCHAR Type, FName Name, FStringView Before, FStringView After)
{ {
Token T; Token T;
T.Type = Type; T.Type = Type;
T.Name = Name; T.Name = Name;
T.Source = FStringView(Before.GetData(), After.GetData() - Before.GetData());
Tokens.Add(T); Tokens.Add(T);
} }
void WingTokenizer::Add(TCHAR Type, FStringView Rest) void WingTokenizer::Add(TCHAR Type, FStringView Rest, FStringView Before, FStringView After)
{ {
Token T; Token T;
T.Type = Type; T.Type = Type;
T.Rest = Rest; T.Rest = Rest;
T.Source = FStringView(Before.GetData(), After.GetData() - Before.GetData());
Tokens.Add(T); Tokens.Add(T);
} }
@@ -125,6 +127,16 @@ TCHAR WingTokenizer::TokenizeEscapeSequence(FStringView &Rest, FString &Error)
return Result; 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) FName WingTokenizer::TokenizeIdentifier(FStringView &Rest, FString &Error)
{ {
if (!Error.IsEmpty()) return FName(); if (!Error.IsEmpty()) return FName();
@@ -185,21 +197,31 @@ WingTokenizer::WingTokenizer(const FString& In)
Rest = Rest.RightChop(1); Rest = Rest.RightChop(1);
continue; continue;
} }
FStringView Before = Rest;
if (Ch == '=') if (Ch == '=')
{ {
Add(RestOfLine, Rest.RightChop(1)); FStringView Body = Rest.RightChop(1);
Rest = Rest.RightChop(Rest.Len());
Add(RestOfLine, Body, Before, Rest);
break; break;
} }
if (Ch == '/')
{
FStringView Asset = TokenizeAssetName(Rest, Error);
Add(AssetName, Asset, Before, Rest);
continue;
}
if ((Ch == '.') || (Ch == '&')) if ((Ch == '.') || (Ch == '&'))
{ {
Add(Identifier, TokenizeIdentifier(Rest, Error)); FName Id = TokenizeIdentifier(Rest, Error);
Add(Identifier, Id, Before, Rest);
continue; continue;
} }
Cat Category = WingCharacterClasses::GetCat(Ch); Cat Category = WingCharacterClasses::GetCat(Ch);
if (Category == Cat::Punctuation) if (Category == Cat::Punctuation)
{ {
Add(Ch, FString());
Rest = Rest.RightChop(1); Rest = Rest.RightChop(1);
Add(Ch, FString(), Before, Rest);
continue; continue;
} }
if (Category == Cat::Control) if (Category == Cat::Control)
@@ -207,17 +229,38 @@ WingTokenizer::WingTokenizer(const FString& In)
Error = "Control characters in input, not allowed"; Error = "Control characters in input, not allowed";
break; break;
} }
Add(Identifier, TokenizeIdentifier(Rest, Error)); FName Id = TokenizeIdentifier(Rest, Error);
Add(Identifier, Id, Before, Rest);
continue; continue;
} }
if (!Error.IsEmpty()) Tokens.Empty(); if (!Error.IsEmpty()) Tokens.Empty();
// Two sentinels means we can safely do lookahead 2 without risk. // Two sentinels means we can safely do lookahead 2 without risk.
Add(0, FName()); Rest = Rest.LeftChop(Rest.Len());
Add(0, FName()); Add(0, FName(), Rest, Rest);
Add(0, FName(), Rest, Rest);
Next = Tokens.GetData(); 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 void WingTokenizer::PrintEverything(FStringBuilderBase &Out) const
{ {
if (!Error.IsEmpty()) 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)); Out.Appendf(TEXT("[%s]"), *FString(T.Rest));
} }

View File

@@ -431,21 +431,16 @@ static const FName NAME_TypeSoft(TEXT("Soft"));
static const FName NAME_TypeClass(TEXT("Class")); static const FName NAME_TypeClass(TEXT("Class"));
static const FName NAME_TypeSoftClass(TEXT("SoftClass")); static const FName NAME_TypeSoftClass(TEXT("SoftClass"));
static const FName NAME_StartOfType("Start-of-Type");
void UWingTypes::PrintParseError(WingTokenizer& Tok, const TCHAR* Message) 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); 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) 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 (!ParseMaybeWrapped(Tok, OutType)) return false;
} }
if (!ParseEOF(Tok)) return false;
return true; 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<UWingTypes>();
check(Types);
Tok.SaveCursor(NAME_StartOfType);
if (!Require.BlueprintType.IsSet() || if (!Require.BlueprintType.IsSet() ||
!Require.Blueprintable.IsSet() || !Require.Blueprintable.IsSet() ||
!Require.AllowContainer.IsSet()) !Require.AllowContainer.IsSet())
@@ -621,19 +619,23 @@ bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, co
return false; return false;
} }
UWingTypes* Types = GEditor->GetEditorSubsystem<UWingTypes>();
check(Types);
WingTokenizer Tok(Text);
OutPinType = FEdGraphPinType(); OutPinType = FEdGraphPinType();
if (!Types->ParseType(Tok, OutPinType)) if (!Types->ParseType(Tok, OutPinType))
{ {
OutPinType = FEdGraphPinType(); return false; 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 (!Require.AllowContainer.GetValue())
{ {
if (OutPinType.IsContainer()) 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::Printf(TEXT("ERROR: Type '%s' is a container, not allowed here\n"), *Text);
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
@@ -665,6 +667,7 @@ bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, co
{ {
if (!IsBlueprintType(OutPinType)) if (!IsBlueprintType(OutPinType))
{ {
FString Text(Tok.GetRange(NAME_StartOfType, 0));
UWingServer::Printf(TEXT("ERROR: Not a blueprint type: %s\n"), *Text); UWingServer::Printf(TEXT("ERROR: Not a blueprint type: %s\n"), *Text);
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
@@ -675,6 +678,7 @@ bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, co
{ {
if (!IsBlueprintable(OutPinType)) if (!IsBlueprintable(OutPinType))
{ {
FString Text(Tok.GetRange(NAME_StartOfType, 0));
UWingServer::Printf(TEXT("ERROR: Not a blueprintable type: %s\n"), *Text); UWingServer::Printf(TEXT("ERROR: Not a blueprintable type: %s\n"), *Text);
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
@@ -684,6 +688,12 @@ bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, co
return true; 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) UClass* UWingTypes::TextToOneObjectType(const FString& Text, const Requirements &Require)
{ {
FEdGraphPinType PinType; FEdGraphPinType PinType;

View File

@@ -2,8 +2,13 @@
#include "WingServer.h" #include "WingServer.h"
#include "WingTypes.h" #include "WingTypes.h"
#include "WingUtils.h" #include "WingUtils.h"
#include "WingProperty.h"
#include "WingTokenizer.h"
#include "EdGraphSchema_K2.h" #include "EdGraphSchema_K2.h"
#include "K2Node_FunctionEntry.h" #include "K2Node_FunctionEntry.h"
#include "K2Node_FunctionResult.h"
#include "K2Node_Tunnel.h"
#include "K2Node_EditablePinBase.h"
#include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/BlueprintEditorUtils.h"
// Flag names used for blueprint variables. // 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); 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; WingVariables Result;
Result.Name = Desc.VarName; Result.Category = Cat::Blueprint;
Result.Type = Desc.VarType; Result.Blueprint = BP;
for (FBPVariableDescription& Desc : BP->NewVariables)
{
// Skip event dispatchers.
if (Desc.VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate) continue;
if (!(Desc.PropertyFlags & CPF_DisableEditOnInstance)) // Parse the bulk of the flags.
Result.AddFlagIfRelevant(Flag_InstanceEditable, Category); Var V = ParseVariableDescription(Desc, Cat::Blueprint);
V.BPVar = &Desc;
if (Desc.PropertyFlags & CPF_BlueprintReadOnly) // Read default value from CDO if available.
Result.AddFlagIfRelevant(Flag_BlueprintReadOnly, Category); if (BP->GeneratedClass)
{
if (Desc.PropertyFlags & CPF_Interp) UObject* CDO = BP->GeneratedClass->GetDefaultObject();
Result.AddFlagIfRelevant(Flag_ExposeToCinematics, Category); FProperty* Prop = BP->GeneratedClass->FindPropertyByName(Desc.VarName);
if (CDO && Prop)
if (Desc.HasMetaData(FBlueprintMetadata::MD_ExposeOnSpawn)) V.DefaultValue = FWingProperty(Prop, CDO).GetText();
Result.AddFlagIfRelevant(Flag_ExposeOnSpawn, Category); }
if (Desc.HasMetaData(FBlueprintMetadata::MD_Private))
Result.AddFlagIfRelevant(Flag_Private, Category);
Result.Variables.Add(MoveTemp(V));
}
return Result; return Result;
} }
WingVariables WingVariables::ParseFunctionLocalVariables(UK2Node_EditablePinBase *Node)
{
WingVariables Result;
if (UK2Node_FunctionEntry *Func = Cast<UK2Node_FunctionEntry>(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<FUserPinInfo>& 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<UK2Node_FunctionEntry>(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<FUserPinInfo> *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) bool WingVariables::CheckSanity(Cat Category)
{ {
const TSet<FName> &Relevant = GetRelevantFlagSet(Category); const TSet<FName> &Relevant = GetRelevantFlagSet(Category);
@@ -94,42 +236,113 @@ bool WingVariables::CheckSanity(Cat Category)
} }
WingVariables WingVariables::ParseBlueprintVariables(const UBlueprint *BP) bool WingVariables::ParseVariableFlags(WingTokenizer &Tok, TSet<FName> &Out)
{ {
WingVariables Result; Tok.Advance(); // Step over open-paren
for (const FBPVariableDescription& Desc : BP->NewVariables) while (Tok.TokenIs(Tok.Identifier))
{ {
// Skip event dispatchers. Out.Add(Tok.NextName());
if (Desc.VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate) continue; Tok.Advance();
// Commas are optional.
// Parse the bulk of the flags. if (Tok.TokenIs(',')) Tok.Advance();
Var V = ParseVariableDescription(Desc, Cat::Blueprint); }
if (!Tok.TokenIs(')'))
// Read default value from CDO if available.
if (BP->GeneratedClass)
{ {
UObject* CDO = BP->GeneratedClass->GetDefaultObject(); Tok.SaveCursor(NAME_None);
FProperty* Prop = BP->GeneratedClass->FindPropertyByName(Desc.VarName); UWingServer::Printf(TEXT("ERROR: flag list contains invalid token '%s'\n"),
if (CDO && Prop) *FString(Tok.GetRange(NAME_None, 1)));
V.DefaultValue = FWingProperty(Prop, CDO).GetText(); return false;
}
Tok.Advance(); // Step over close-paren
return true;
} }
Result.Variables.Add(MoveTemp(V)); bool WingVariables::ParseOneVariable(WingTokenizer &Tok, Var &V)
{
// Parse type.
UWingTypes::Requirements Req;
Req.BlueprintType = true;
Req.Blueprintable = false;
Req.AllowContainer = true;
if (!UWingTypes::TextToType(Tok, V.Type, Req, false))
return false;
// Parse name.
if (Tok.NextType() != Tok.Identifier)
{
UWingServer::Print(TEXT("ERROR: Expected variable name after type\n"));
return false;
} }
return Result; V.Name = Tok.NextName();
Tok.Advance();
// Parse optional flags: (flag1, flag2, ...)
if (Tok.TokenIs('('))
{
if (!ParseVariableFlags(Tok, V.Flags)) return false;
} }
WingVariables WingVariables::ParseFunctionLocalVariables(const UK2Node_FunctionEntry *Func) // Parse optional default value: = rest-of-line
if (Tok.NextType() == Tok.RestOfLine)
{ {
WingVariables Result; V.DefaultSpecified = true;
if (!Func) return Result; V.DefaultValue = FString(Tok.NextRest().TrimStartAndEnd());
Tok.Advance();
for (const FBPVariableDescription& Desc : Func->LocalVariables)
{
Var V = ParseVariableDescription(Desc, Cat::FunctionLocal);
V.DefaultValue = Desc.DefaultValue;
Result.Variables.Add(MoveTemp(V));
} }
// 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<FString> 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; return Result;
} }
@@ -170,3 +383,154 @@ void WingVariables::PrintAll(int32 Indent)
{ {
PrintAll(UWingServer::GetPrintBuffer(), 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<UK2Node_EditablePinBase> EntryNode;
TWeakObjectPtr<UK2Node_EditablePinBase> 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<UK2Node_EditablePinBase> EntryNode;
TWeakObjectPtr<UK2Node_EditablePinBase> 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;
}

View File

@@ -103,13 +103,15 @@ struct WingTokenizer
// //
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
const TCHAR Identifier = 'i'; static const TCHAR Identifier = 'i';
const TCHAR RestOfLine = 'r'; static const TCHAR AssetName = 'a';
static const TCHAR RestOfLine = 'r';
// Get the next token. // Get the next token.
TCHAR NextType() const { return Next[0].Type; } TCHAR NextType() const { return Next[0].Type; }
FName NextName() const { return Next[0].Name; } FName NextName() const { return Next[0].Name; }
FStringView NextRest() const { return Next[0].Rest; } FStringView NextRest() const { return Next[0].Rest; }
FStringView NextSource() const { return Next[0].Source; }
// Check the next token. // Check the next token.
bool TokenIs(TCHAR Type) const { return Next[0].Type == Type; } bool TokenIs(TCHAR Type) const { return Next[0].Type == Type; }
@@ -123,8 +125,12 @@ struct WingTokenizer
// Advance the cursor. Don't move past the two sentinels. // Advance the cursor. Don't move past the two sentinels.
void Advance() { int I = Next-Tokens.GetData(); if (I + 2 < Tokens.Num()) Next++; } void Advance() { int I = Next-Tokens.GetData(); if (I + 2 < Tokens.Num()) Next++; }
// Get the original input string. // Save the current cursor position, giving the save a name.
const FString& GetInput() const { return Input; } 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 // Tokenize a line of input. The tokens are stored in
// the token array, and the cursor is positioned on the first // the token array, and the cursor is positioned on the first
@@ -176,8 +182,9 @@ private:
struct Token struct Token
{ {
FName Name; // Only if it's an identifier 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 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. // The string that we tokenized.
@@ -192,6 +199,9 @@ private:
// The cursor which advances along the tokens. Never moves past sentinels. // The cursor which advances along the tokens. Never moves past sentinels.
Token *Next; Token *Next;
// A table of saved cursor positions.
TArray<TPair<FName, int>, TInlineAllocator<3>> SavedCursor;
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// //
// Internal Implementation Functions. // Internal Implementation Functions.
@@ -200,15 +210,22 @@ private:
using Cat = WingCharacterClasses::Cat; using Cat = WingCharacterClasses::Cat;
// Add a token to the token array. // Add a token to the token array. Before/After are the Rest
void Add(TCHAR Type, FName InternalID); // string view before and after tokenization; the difference
void Add(TCHAR Type, FStringView Rest); // 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 // Convert numbers to TCHAR. If there's an error, set the error
// message and return zero. // message and return zero.
static TCHAR FromHex(FStringView Digits, FString &Error); static TCHAR FromHex(FStringView Digits, FString &Error);
static TCHAR FromDecimal(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 // Tokenize an escape sequence. Attempts to consume a valid escape
// sequence from rest, and return the character indicated. On error, // sequence from rest, and return the character indicated. On error,
// sets the error message and returns zero. // sets the error message and returns zero.

View File

@@ -118,7 +118,6 @@ private:
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
static void PrintParseError(WingTokenizer& Tok, const TCHAR* Message); static void PrintParseError(WingTokenizer& Tok, const TCHAR* Message);
static bool ParseEOF(WingTokenizer& Tok);
static bool ParseChar(WingTokenizer& Tok, TCHAR c); static bool ParseChar(WingTokenizer& Tok, TCHAR c);
bool ParsePlainIdentifier(WingTokenizer& Tok, FEdGraphPinType& OutType); bool ParsePlainIdentifier(WingTokenizer& Tok, FEdGraphPinType& OutType);
bool ParseWrapped(WingTokenizer& Tok, FName Wrapper, 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 // Parse a type. If it doesn't parse, or if the type doesn't satisfy the
// requirements, prints an error and returns false. // requirements, prints an error and returns false.
static bool TextToType(const FString& Text, FEdGraphPinType& OutPinType, const Requirements &Require); static bool TextToType(const FString& Text, FEdGraphPinType& OutPinType, const Requirements &Require);

View File

@@ -14,6 +14,7 @@
#include "K2Node_EditablePinBase.h" #include "K2Node_EditablePinBase.h"
#include "Components/Widget.h" #include "Components/Widget.h"
#include "WingActorComponent.h" #include "WingActorComponent.h"
#include "WingVariables.h"
struct FEdGraphSchemaAction; struct FEdGraphSchemaAction;
class UAnimationStateMachineGraph; class UAnimationStateMachineGraph;
@@ -60,8 +61,10 @@ public:
static FName GetFName(const FProperty *Prop) { return Prop->GetFName(); } static FName GetFName(const FProperty *Prop) { return Prop->GetFName(); }
static FName GetFName(const FWingProperty &Prop); static FName GetFName(const FWingProperty &Prop);
static FName GetFName(const FUserPinInfo &Pin) { return Pin.PinName; } static FName GetFName(const FUserPinInfo &Pin) { return Pin.PinName; }
static FName GetFName(const TSharedPtr<FUserPinInfo> &Pin) { return Pin->PinName; }
static FName GetFName(const UWingComponentReference *Ref) { return Ref->VariableName; } static FName GetFName(const UWingComponentReference *Ref) { return Ref->VariableName; }
static FName GetFName(const UWidget *Widget) { return Widget->GetFName(); } 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); return FindNoneWithInternalID(InternalID, Array, Kind);
} }
static bool FindNoDuplicateName(TSet<FName> &Collection, FName InternalID, const TCHAR *Kind)
{
if (Collection.Contains(InternalID))
{ CheckExactlyOneNamed(2, Kind, InternalID); return false; }
return true;
}
template<typename ArrayType>
static bool FindNoDuplicateNames(TSet<FName> &Collection, ArrayType &&Array, const TCHAR *Kind)
{
for (auto &Elt : Array)
if (!FindNoDuplicateName(Collection, GetFName(Elt), Kind)) return false;
return true;
}
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
// //
// Name Formatting // Name Formatting

View File

@@ -1,20 +1,23 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "WingProperty.h" #include "EdGraph/EdGraphPin.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "K2Node_EditablePinBase.h"
class UK2Node_FunctionEntry; struct WingTokenizer;
class WingVariables class WingVariables
{ {
public:
enum class Cat enum class Cat
{ {
Empty,
Blueprint, Blueprint,
FunctionLocal LocalVariable,
EditablePinBase,
}; };
private:
struct Var struct Var
{ {
// Internal name. // Internal name.
@@ -26,18 +29,41 @@ private:
// Default Value. // Default Value.
FString DefaultValue; FString DefaultValue;
// When parsing a string, true if default was specified.
bool DefaultSpecified = true;
// Boolean flags. // Boolean flags.
TSet<FName> Flags; TSet<FName> 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<FUserPinInfo> Pin = nullptr;
// Add the flag if it's relevant to category C. // Add the flag if it's relevant to category C.
void AddFlagIfRelevant(FName Flag, Cat C); void AddFlagIfRelevant(FName Flag, Cat C);
}; };
private: private:
// The category of these variables.
Cat Category;
// A list of all the variables. // A list of all the variables.
TArray<Var> Variables; TArray<Var> Variables;
// A pointer to a blueprint.
UBlueprint *Blueprint = nullptr;
// A pointer to an editable pin base.
UK2Node_EditablePinBase* PinBase = nullptr;
public: public:
// Get the variables.
const TArray<Var> GetVariables() { return Variables; }
// Returns the set of flags that are supported by this variable category. // Returns the set of flags that are supported by this variable category.
static const TSet<FName> &GetRelevantFlagSet(Cat C); static const TSet<FName> &GetRelevantFlagSet(Cat C);
@@ -53,11 +79,52 @@ public:
// Make sure that this variable list makes sense for the given context. // Make sure that this variable list makes sense for the given context.
bool CheckSanity(Cat Category); 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. // Parse variables from a given source.
static WingVariables ParseBlueprintVariables(const UBlueprint *BP); static WingVariables ParseBlueprintVariables(UBlueprint *BP);
static WingVariables ParseFunctionLocalVariables(const UK2Node_FunctionEntry *F); 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: private:
static Var ParseVariableDescription(const FBPVariableDescription &V, Cat C); static Var ParseVariableDescription(const FBPVariableDescription &V, Cat C);
static bool ParseOneVariable(WingTokenizer &Tok, Var &Out);
static bool ParseVariableFlags(WingTokenizer &Tok, TSet<FName> &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);
}; };

View File

@@ -20,6 +20,25 @@ def main():
print("Usage: ue-wingman.py ShowCommands [key=value ...]") print("Usage: ue-wingman.py ShowCommands [key=value ...]")
sys.exit(1) sys.exit(1)
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:
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]} msg = {"command": args[0]}
for arg in args[1:]: for arg in args[1:]:
key, _, value = arg.partition("=") key, _, value = arg.partition("=")