More refactoring in MCP

This commit is contained in:
2026-03-20 19:40:29 -04:00
parent ca6b3f9768
commit 90b35f785e
17 changed files with 166 additions and 184 deletions

View File

@@ -51,7 +51,7 @@ public:
if (!BP) return; if (!BP) return;
// Check graph name uniqueness // Check graph name uniqueness
if (!WingUtils::FindExactlyNoneNamed(Graph, WingUtils::AllGraphs(BP))) if (!WingUtils::FindExactlyNoneNamed(Graph, WingUtils::AllGraphs(BP), TEXT("Graph")))
return; return;
// For custom events, also check for existing custom events with the same name // For custom events, also check for existing custom events with the same name

View File

@@ -71,7 +71,7 @@ public:
} }
// Check against existing graphs (functions, macros, etc.) // Check against existing graphs (functions, macros, etc.)
if (!WingUtils::FindExactlyNoneNamed(DispatcherName, WingUtils::AllGraphs(BP))) if (!WingUtils::FindExactlyNoneNamed(DispatcherName, WingUtils::AllGraphs(BP), TEXT("Graph")))
return; return;
// Add a member variable with PC_MCDelegate pin type // Add a member variable with PC_MCDelegate pin type

View File

@@ -56,22 +56,22 @@ public:
return; return;
} }
// Check for duplicate component names (including native/inherited) // Check that the proposed name is valid
TArray<FWingActorComponent> AllComponents = FWingActorComponent::GetAll(BP); TArray<FWingActorComponent> AllComponents = FWingActorComponent::GetAll(BP);
if (!WingUtils::FindExactlyNoneNamed(Component, AllComponents)) if (!WingUtils::FindExactlyNoneNamed(Component, AllComponents, TEXT("Component"))) return;
return; FString InternalName = WingUtils::CheckProposedName(Component);
if (InternalName.IsEmpty()) return;
// Resolve the component class by name // Resolve the component class by name
UClass* ComponentClass = UWingTypes::TextToOneObjectType(Class); UClass* ComponentClass = UWingTypes::TextToOneObjectType(Class);
if (!ComponentClass) return; if (!ComponentClass) return;
// Find the specified parent component // Find the specified parent component
FWingActorComponent* ParentComp = WingUtils::FindExactlyOneNamed(Parent, AllComponents); FWingActorComponent* ParentComp = WingUtils::FindExactlyOneNamed(Parent, AllComponents, TEXT("Component"));
if (!ParentComp) return; if (!ParentComp) return;
// Create the SCS node // Create the SCS node
FString NewName = WingUtils::UnsanitizeName(Component); USCS_Node *NewNode = FWingActorComponent::AddComponent(BP, ComponentClass, ParentComp, InternalName);
USCS_Node *NewNode = FWingActorComponent::AddComponent(BP, ComponentClass, ParentComp, NewName);
if (!NewNode) if (!NewNode)
{ {
UWingServer::Printf(TEXT("ERROR: Failed to create SCS node for component '%s' with class '%s'\n"), UWingServer::Printf(TEXT("ERROR: Failed to create SCS node for component '%s' with class '%s'\n"),

View File

@@ -44,7 +44,7 @@ public:
// Find the new parent among all components (if specified) // Find the new parent among all components (if specified)
UBlueprint *BP = SCS->GetBlueprint(); UBlueprint *BP = SCS->GetBlueprint();
TArray<FWingActorComponent> AllComponents = FWingActorComponent::GetAll(BP); TArray<FWingActorComponent> AllComponents = FWingActorComponent::GetAll(BP);
FWingActorComponent* NewParent = WingUtils::FindExactlyOneNamed(Parent, AllComponents); FWingActorComponent* NewParent = WingUtils::FindExactlyOneNamed(Parent, AllComponents, TEXT("Component"));
if (!NewParent) return; if (!NewParent) return;
if (!FWingActorComponent::ReparentComponent(BP, if (!FWingActorComponent::ReparentComponent(BP,

View File

@@ -5,7 +5,7 @@
#include "WingHandler.h" #include "WingHandler.h"
#include "WingFetcher.h" #include "WingFetcher.h"
#include "WingJson.h" #include "WingJson.h"
#include "WingBlueprintVar.h" #include "WingFunctionArgs.h"
#include "WingUtils.h" #include "WingUtils.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "EdGraphSchema_K2.h" #include "EdGraphSchema_K2.h"
@@ -29,12 +29,12 @@ public:
UPROPERTY(meta=(Description="Name of the new event dispatcher")) UPROPERTY(meta=(Description="Name of the new event dispatcher"))
FString Name; FString Name;
UPROPERTY(meta=(Optional, Description="Configuration: DelegateArgs, Category, Description, InstanceEditable, BlueprintReadOnly, Private, etc.")) UPROPERTY(meta=(Description="Arguments expressed as: int x,float y"))
FWingJsonObject Config; FString Arguments;
virtual FString GetDescription() const override virtual FString GetDescription() const override
{ {
return TEXT("Add a new event dispatcher to a Blueprint. Pass Config to set parameters, category, flags, etc."); return TEXT("Add a new event dispatcher to a Blueprint.");
} }
virtual void Handle() override virtual void Handle() override
@@ -43,21 +43,15 @@ public:
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;
// Check for duplicate variable name // Check for valid proposed name
FName VarFName(*WingUtils::UnsanitizeName(Name)); if (!WingUtils::FindExactlyNoneNamed(Name, BP->NewVariables, TEXT("Variable"))) return;
if (FBlueprintEditorUtils::FindNewVariableIndex(BP, VarFName) != INDEX_NONE) if (!WingUtils::FindExactlyNoneNamed(Name, WingUtils::AllGraphs(BP), TEXT("Graph"))) return;
{ FString InternalName = WingUtils::CheckProposedName(Name);
UWingServer::Printf(TEXT("ERROR: Variable or dispatcher '%s' already exists in %s\n"), *Name, *WingUtils::FormatName(BP)); if (InternalName.IsEmpty()) return;
return; FName VarFName(InternalName);
}
for (UEdGraph* Graph : WingUtils::AllGraphs(BP)) // Make sure the argument types are valid.
{ if (!WingFunctionArgs::CheckArgs(Arguments)) return;
if (Graph->GetFName() == VarFName)
{
UWingServer::Printf(TEXT("ERROR: A graph named '%s' already exists in %s\n"), *Name, *WingUtils::FormatName(BP));
return;
}
}
// Add the delegate variable // Add the delegate variable
FEdGraphPinType DelegateType; FEdGraphPinType DelegateType;
@@ -85,15 +79,16 @@ public:
K2Schema->MarkFunctionEntryAsEditable(SigGraph, true); K2Schema->MarkFunctionEntryAsEditable(SigGraph, true);
BP->DelegateSignatureGraphs.Add(SigGraph); BP->DelegateSignatureGraphs.Add(SigGraph);
// Find the newly created variable and apply config // Store the function arguments
FWingBlueprintVar Editor(BP, Name); TWeakObjectPtr<UK2Node_EditablePinBase> EntryNode;
if (Editor.NotFound()) return; TWeakObjectPtr<UK2Node_EditablePinBase> ResultNode;
FBlueprintEditorUtils::GetEntryAndResultNodes(SigGraph, EntryNode, ResultNode);
if (Config.Json && Config.Json->Values.Num() > 0) if (!EntryNode.IsValid())
{ {
if (!Editor.ApplyJson(Config.Json.Get())) UWingServer::Print(TEXT("ERROR: Entry node not found in delegate signature graph\n"));
return; return;
} }
if (!WingFunctionArgs::SetArgs(EntryNode.Get(), Arguments)) return;
UWingServer::Printf(TEXT("Created event dispatcher %s in %s\n"), *Name, *WingUtils::FormatName(BP)); UWingServer::Printf(TEXT("Created event dispatcher %s in %s\n"), *Name, *WingUtils::FormatName(BP));
} }

View File

@@ -7,6 +7,8 @@
#include "WingUtils.h" #include "WingUtils.h"
#include "WingBlueprintVar.h" #include "WingBlueprintVar.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "WingFunctionArgs.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "BlueprintDispatcher_Dump.generated.h" #include "BlueprintDispatcher_Dump.generated.h"
@@ -37,15 +39,22 @@ public:
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;
FWingBlueprintVar Editor(BP, Dispatcher); FBPVariableDescription* Var = WingUtils::FindExactlyOneNamed(Dispatcher, BP->NewVariables, TEXT("Dispatcher"));
if (Editor.NotFound()) return; if (!Var) return;
if (!Editor.IsEventDispatcher()) TObjectPtr<UEdGraph>* SigGraph = WingUtils::FindExactlyOneNamed(Dispatcher, BP->DelegateSignatureGraphs, TEXT("Dispatcher Signature Graph"));
if (!SigGraph) return;
TWeakObjectPtr<UK2Node_EditablePinBase> EntryNode;
TWeakObjectPtr<UK2Node_EditablePinBase> ResultNode;
FBlueprintEditorUtils::GetEntryAndResultNodes(*SigGraph, EntryNode, ResultNode);
if (!EntryNode.IsValid())
{ {
UWingServer::Printf(TEXT("ERROR: '%s' is not an event dispatcher\n"), *Dispatcher); UWingServer::Print(TEXT("ERROR: Entry node not found in delegate signature graph\n"));
return; return;
} }
FString Args = WingFunctionArgs::GetArgs(EntryNode.Get());
UWingServer::Printf(TEXT("Event dispatcher %s in %s:\n"), *Dispatcher, *WingUtils::FormatName(BP)); UWingServer::Printf(TEXT("Event dispatcher %s in %s:\n"), *Dispatcher, *WingUtils::FormatName(BP));
Editor.Dump(); UWingServer::Printf(TEXT(" Args: %s\n"), *Args);
} }
}; };

View File

@@ -45,13 +45,11 @@ public:
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;
// Check for duplicate variable name // Check validity of the proposed name
FName VarFName(*WingUtils::UnsanitizeName(Name)); if (!WingUtils::FindExactlyNoneNamed(Name, BP->NewVariables, TEXT("Variable"))) return;
if (FBlueprintEditorUtils::FindNewVariableIndex(BP, VarFName) != INDEX_NONE) FString InternalName = WingUtils::CheckProposedName(Name);
{ if (InternalName.IsEmpty()) return;
UWingServer::Printf(TEXT("ERROR: Variable '%s' already exists in %s\n"), *Name, *WingUtils::FormatName(BP)); FName VarFName(InternalName);
return;
}
// Add the variable with a default type // Add the variable with a default type
FEdGraphPinType DefaultType; FEdGraphPinType DefaultType;

View File

@@ -43,7 +43,7 @@ public:
// Find the interface by name // Find the interface by name
FBPInterfaceDescription *IFaceDesc = FBPInterfaceDescription *IFaceDesc =
WingUtils::FindExactlyOneNamed(Interface, BP->ImplementedInterfaces); WingUtils::FindExactlyOneNamed(Interface, BP->ImplementedInterfaces, TEXT("Interface"));
if (!IFaceDesc) return; if (!IFaceDesc) return;
UClass* FoundInterface = IFaceDesc->Interface; UClass* FoundInterface = IFaceDesc->Interface;

View File

@@ -1,38 +1,22 @@
#include "WingBlueprintVar.h" #include "WingBlueprintVar.h"
#include "WingFunctionArgs.h"
#include "WingJson.h" #include "WingJson.h"
#include "WingServer.h" #include "WingServer.h"
#include "WingTypes.h" #include "WingTypes.h"
#include "WingUtils.h" #include "WingUtils.h"
#include "EdGraphSchema_K2.h" #include "EdGraphSchema_K2.h"
#include "K2Node_EditablePinBase.h"
#include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/BlueprintEditorUtils.h"
FWingBlueprintVar::FWingBlueprintVar(UBlueprint* BP, const FString& VarName) FWingBlueprintVar::FWingBlueprintVar(UBlueprint* BP, const FString& VarName)
{ {
Desc = WingUtils::FindExactlyOneNamed(VarName, BP->NewVariables); Desc = WingUtils::FindExactlyOneNamed(VarName, BP->NewVariables, TEXT("Variable"));
if (!Desc) return; if (!Desc) return;
if (Desc->VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate) if (Desc->VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate)
{ {
// Find the matching signature graph by name. UWingServer::Printf(TEXT("Cannot edit event dispatchers using BlueprintVariable functions.\n"));
for (UEdGraph* Graph : BP->DelegateSignatureGraphs)
{
if (Graph->GetFName() == Desc->VarName)
{
SigGraph = Graph;
break;
}
}
if (!SigGraph)
{
UWingServer::Printf(TEXT("ERROR: Signature graph not found for event dispatcher '%s'\n"), *VarName);
Desc = nullptr; Desc = nullptr;
return; return;
} }
}
else
{
// Try to find the default value property on the CDO. // Try to find the default value property on the CDO.
if (BP->GeneratedClass) if (BP->GeneratedClass)
{ {
@@ -42,13 +26,11 @@ FWingBlueprintVar::FWingBlueprintVar(UBlueprint* BP, const FString& VarName)
DefaultValueProp = FWingProperty(Prop, CDO); DefaultValueProp = FWingProperty(Prop, CDO);
} }
} }
}
void FWingBlueprintVar::Dump() void FWingBlueprintVar::Dump()
{ {
LoadFlags(); LoadFlags();
LoadDefault(); LoadDefault();
LoadDelegateArgs();
TArray<FWingProperty> Props = MergedProperties(); TArray<FWingProperty> Props = MergedProperties();
for (FWingProperty& P : Props) for (FWingProperty& P : Props)
{ {
@@ -62,7 +44,6 @@ void FWingBlueprintVar::Dump()
bool FWingBlueprintVar::ApplyJson(const FJsonObject* Json) bool FWingBlueprintVar::ApplyJson(const FJsonObject* Json)
{ {
bool bHasDefault = Json->HasField(TEXT("DefaultValue")); bool bHasDefault = Json->HasField(TEXT("DefaultValue"));
bool bHasDelegateArgs = Json->HasField(TEXT("DelegateArgs"));
bool bHasType = Json->HasField(TEXT("VarType")); bool bHasType = Json->HasField(TEXT("VarType"));
if (bHasDefault && bHasType) if (bHasDefault && bHasType)
{ {
@@ -74,7 +55,6 @@ bool FWingBlueprintVar::ApplyJson(const FJsonObject* Json)
} }
LoadFlags(); LoadFlags();
LoadDelegateArgs();
TArray<FWingProperty> Props = MergedProperties(); TArray<FWingProperty> Props = MergedProperties();
if (!WingJson::PopulateFromJson(Props, Json, true)) if (!WingJson::PopulateFromJson(Props, Json, true))
@@ -82,9 +62,7 @@ bool FWingBlueprintVar::ApplyJson(const FJsonObject* Json)
SaveFlags(); SaveFlags();
if (bHasDefault) if (bHasDefault)
if (!SaveDefault()) return false; return SaveDefault();
if (bHasDelegateArgs)
if (!SaveDelegateArgs()) return false;
return true; return true;
} }
@@ -110,18 +88,6 @@ void FWingBlueprintVar::LoadDefault()
DefaultValue.Empty(); DefaultValue.Empty();
} }
void FWingBlueprintVar::LoadDelegateArgs()
{
if (!SigGraph) return;
TWeakObjectPtr<UK2Node_EditablePinBase> EntryNode;
TWeakObjectPtr<UK2Node_EditablePinBase> ResultNode;
FBlueprintEditorUtils::GetEntryAndResultNodes(SigGraph, EntryNode, ResultNode);
if (EntryNode.IsValid())
DelegateArgs = WingFunctionArgs::GetArgs(EntryNode.Get());
else
DelegateArgs.Empty();
}
void FWingBlueprintVar::SaveFlags() void FWingBlueprintVar::SaveFlags()
{ {
// CPF flags // CPF flags
@@ -165,20 +131,6 @@ bool FWingBlueprintVar::SaveDefault()
return true; return true;
} }
bool FWingBlueprintVar::SaveDelegateArgs()
{
if (!SigGraph) return true;
TWeakObjectPtr<UK2Node_EditablePinBase> EntryNode;
TWeakObjectPtr<UK2Node_EditablePinBase> ResultNode;
FBlueprintEditorUtils::GetEntryAndResultNodes(SigGraph, EntryNode, ResultNode);
if (!EntryNode.IsValid())
{
UWingServer::Print(TEXT("ERROR: Entry node not found in delegate signature graph\n"));
return false;
}
return WingFunctionArgs::SetArgs(EntryNode.Get(), DelegateArgs);
}
TArray<FWingProperty> FWingBlueprintVar::MergedProperties() TArray<FWingProperty> FWingBlueprintVar::MergedProperties()
{ {
TArray<FWingProperty> Props = FWingProperty::GetAll( TArray<FWingProperty> Props = FWingProperty::GetAll(
@@ -193,20 +145,9 @@ TArray<FWingProperty> FWingBlueprintVar::MergedProperties()
Props.Append(FWingProperty::GetAll( Props.Append(FWingProperty::GetAll(
FWingBlueprintVar::StaticStruct(), this, (EPropertyFlags)0)); FWingBlueprintVar::StaticStruct(), this, (EPropertyFlags)0));
if (SigGraph)
{
FWingProperty::Remove(Props, TEXT("VarType"));
FWingProperty::Remove(Props, TEXT("DefaultValue"));
FWingProperty::Remove(Props, TEXT("ExposeOnSpawn"));
FWingProperty::Remove(Props, TEXT("ExposeToCinematics"));
}
else
{
FWingProperty::Remove(Props, TEXT("DelegateArgs"));
// Remove DefaultValue if we don't have a CDO property to back it. // Remove DefaultValue if we don't have a CDO property to back it.
if (!DefaultValueProp) if (!DefaultValueProp)
FWingProperty::Remove(Props, TEXT("DefaultValue")); FWingProperty::Remove(Props, TEXT("DefaultValue"));
}
return Props; return Props;
} }

View File

@@ -195,7 +195,7 @@ WingFetcher& WingFetcher::Graph(const FString& Value)
if (!BP) if (!BP)
return TypeMismatch(TEXT("graph"), TEXT("Blueprint or Material")); return TypeMismatch(TEXT("graph"), TEXT("Blueprint or Material"));
UEdGraph* Graph = WingUtils::FindExactlyOneNamed(Value, WingUtils::AllGraphs(BP)); UEdGraph* Graph = WingUtils::FindExactlyOneNamed(Value, WingUtils::AllGraphs(BP), TEXT("Graph"));
if (!Graph) return SetError(); if (!Graph) return SetError();
SetObj(Graph); SetObj(Graph);
@@ -300,7 +300,7 @@ WingFetcher& WingFetcher::Component(const FString& Value)
return TypeMismatch(TEXT("component"), TEXT("Blueprint")); return TypeMismatch(TEXT("component"), TEXT("Blueprint"));
TArray<FWingActorComponent> AllComponents = FWingActorComponent::GetAll(BP); TArray<FWingActorComponent> AllComponents = FWingActorComponent::GetAll(BP);
FWingActorComponent* Comp = WingUtils::FindExactlyOneNamed(Value, AllComponents); FWingActorComponent* Comp = WingUtils::FindExactlyOneNamed(Value, AllComponents, TEXT("Component"));
if (!Comp) return SetError(); if (!Comp) return SetError();
if (!Comp->IsOwnedBy(BP)) if (!Comp->IsOwnedBy(BP))
{ {

View File

@@ -106,3 +106,14 @@ bool WingFunctionArgs::SetArgs(UEdGraphNode* Node, const FString& Args)
Editable->ReconstructNode(); Editable->ReconstructNode();
return true; return true;
} }
bool WingFunctionArgs::CheckArgs(const FString &Args)
{
TArray<FParsedArg> NewArgs;
if (!ParseArgs(Args, NewArgs))
{
UWingServer::Printf(TEXT("Invalid function arguments: %s\n"), *Args);
return false;
}
return true;
}

View File

@@ -121,6 +121,23 @@ void FWingProperty::Remove(TArray<FWingProperty>& Props, const FString& Name)
Props.RemoveAll([&](const FWingProperty& P) { return P.Prop->GetName() == Name; }); Props.RemoveAll([&](const FWingProperty& P) { return P.Prop->GetName() == Name; });
} }
void FWingProperty::Move(TArray<FWingProperty> &Out, TArray<FWingProperty> &In, const FString &Name)
{
int Dst = 0;
for (int i = 0; i < In.Num(); i++)
{
if (In[i]->GetName() == Name)
{
Out.Add(In[i]);
}
else
{
In[Dst++] = In[i];
}
}
In.SetNum(Dst);
}
TArray<FWingProperty> FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags) TArray<FWingProperty> FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
{ {
if (!Obj) return {}; if (!Obj) return {};

View File

@@ -70,15 +70,18 @@ FString WingUtils::SanitizeName(const FString &InName)
FString WingUtils::UnsanitizeName(const FString &InName) FString WingUtils::UnsanitizeName(const FString &InName)
{ {
FString Name = InName; FString Name = InName;
for (int32 i = 0; i < Name.Len(); i++) int32 Dst = 0;
for (int32 Src = 0; Src < Name.Len(); Src++)
{ {
TCHAR c = Name[i]; TCHAR c = Name[Src];
if (c < 0x20 || c == 0x7F) continue;
if (c == L'·') c=' '; if (c == L'·') c=' ';
if (c == L'') c='<'; if (c == L'') c='<';
if (c == L'') c='>'; if (c == L'') c='>';
if (c == L'') c=','; if (c == L'') c=',';
Name[i] = c; Name[Dst++] = c;
} }
Name.LeftInline(Dst);
return Name; return Name;
} }
@@ -113,6 +116,17 @@ FString WingUtils::StandardizeMenuItem(const FString &Item)
return Sanitized; return Sanitized;
} }
FString WingUtils::CheckProposedName(const FString &Name)
{
FString Unsanitized = UnsanitizeName(Name);
if ((Unsanitized.IsEmpty()) || (SanitizeName(Unsanitized) != Name))
{
UWingServer::Printf(TEXT("Names must not contain control characters or be empty\n"));
return FString();
}
return Unsanitized;
}
// ============================================================ // ============================================================
// Name Lookup // Name Lookup
// ============================================================ // ============================================================
@@ -142,6 +156,11 @@ FString WingUtils::FormatName(const UEdGraph *Graph)
return SanitizeName(Graph->GetName()); return SanitizeName(Graph->GetName());
} }
FString WingUtils::FormatName(const TObjectPtr<UEdGraph> &Graph)
{
return SanitizeName(Graph->GetName());
}
FString WingUtils::FormatName(const UEdGraphNode* Node) FString WingUtils::FormatName(const UEdGraphNode* Node)
{ {
return SanitizeName(Node->GetName()); return SanitizeName(Node->GetName());
@@ -335,41 +354,31 @@ bool WingUtils::StringToEnum(UEnum* Enum, const FString& Str, int64& OutValue)
// Common Error Reporting // Common Error Reporting
// ============================================================ // ============================================================
bool WingUtils::CheckExactlyOneNamed(int Count, const FString &Kind, const FString &Name) bool WingUtils::CheckExactlyOneNamed(int Count, const TCHAR *Kind, const FString &Name)
{ {
if (Count == 0) if (Count == 0)
{ {
UWingServer::Printf(TEXT("Could not find a %s named %s.\n"), *Kind, *Name); UWingServer::Printf(TEXT("Could not find a %s named %s.\n"), Kind, *Name);
return false; return false;
} }
if (Count > 1) if (Count > 1)
{ {
UWingServer::Printf(TEXT("More than one %s named %s\n"), *Kind, *Name); UWingServer::Printf(TEXT("More than one %s named %s\n"), Kind, *Name);
return false; return false;
} }
return true; return true;
} }
bool WingUtils::CheckExactlyOneNamed(int Count, UClass *Class, const FString &Name) bool WingUtils::CheckExactlyNoneNamed(int Count, const TCHAR *Kind, const FString &Name)
{
return CheckExactlyOneNamed(Count, Class->GetName(), Name);
}
bool WingUtils::CheckExactlyNoneNamed(int Count, const FString &Kind, const FString &Name)
{ {
if (Count > 0) if (Count > 0)
{ {
UWingServer::Printf(TEXT("A %s named %s already exists."), *Kind, *Name); UWingServer::Printf(TEXT("A %s named %s already exists."), Kind, *Name);
return false; return false;
} }
return true; return true;
} }
bool WingUtils::CheckExactlyNoneNamed(int Count, UClass *Class, const FString &Name)
{
return CheckExactlyNoneNamed(Count, Class->GetName(), Name);
}
// ============================================================ // ============================================================
// Blueprint helpers // Blueprint helpers
// ============================================================ // ============================================================

View File

@@ -16,13 +16,11 @@ struct FWingBlueprintVar
FBPVariableDescription* Desc = nullptr; FBPVariableDescription* Desc = nullptr;
FWingProperty DefaultValueProp; FWingProperty DefaultValueProp;
UEdGraph* SigGraph = nullptr;
FWingBlueprintVar() = default; FWingBlueprintVar() = default;
FWingBlueprintVar(UBlueprint* BP, const FString& VarName); FWingBlueprintVar(UBlueprint* BP, const FString& VarName);
bool NotFound() const { return Desc == nullptr; } bool NotFound() const { return Desc == nullptr; }
bool IsEventDispatcher() const { return SigGraph != nullptr; }
UPROPERTY(EditAnywhere, meta=(Optional, Description="Default value in Unreal text format")) UPROPERTY(EditAnywhere, meta=(Optional, Description="Default value in Unreal text format"))
FString DefaultValue; FString DefaultValue;
@@ -45,9 +43,6 @@ struct FWingBlueprintVar
UPROPERTY(EditAnywhere, meta=(Optional, Description="Expose to cinematics/sequencer")) UPROPERTY(EditAnywhere, meta=(Optional, Description="Expose to cinematics/sequencer"))
bool ExposeToCinematics = false; bool ExposeToCinematics = false;
UPROPERTY(EditAnywhere, meta=(Optional, Description="Delegate parameters, e.g. 'int x,float y'"))
FString DelegateArgs;
// Load from Desc, populate from JSON, save back to Desc. // Load from Desc, populate from JSON, save back to Desc.
bool ApplyJson(const FJsonObject* Json); bool ApplyJson(const FJsonObject* Json);
@@ -57,9 +52,7 @@ struct FWingBlueprintVar
private: private:
void LoadFlags(); void LoadFlags();
void LoadDefault(); void LoadDefault();
void LoadDelegateArgs();
void SaveFlags(); void SaveFlags();
bool SaveDefault(); bool SaveDefault();
bool SaveDelegateArgs();
TArray<FWingProperty> MergedProperties(); TArray<FWingProperty> MergedProperties();
}; };

View File

@@ -20,6 +20,9 @@ struct WingFunctionArgs
// Returns true on success. // Returns true on success.
static bool SetArgs(UEdGraphNode* Node, const FString& Args); static bool SetArgs(UEdGraphNode* Node, const FString& Args);
// Returns true if the arguments are valid, if not prints error.
static bool CheckArgs(const FString &Args);
private: private:
// A parsed argument: type + name. // A parsed argument: type + name.
struct FParsedArg struct FParsedArg
@@ -28,7 +31,7 @@ private:
FName PinName; FName PinName;
}; };
// Parse "int◦x│floaty" into an array of FParsedArg. // Parse "int x,float y" into an array of FParsedArg.
// Returns false and prints an error on failure. // Returns false and prints an error on failure.
static bool ParseArgs(const FString& Args, TArray<FParsedArg>& OutArgs); static bool ParseArgs(const FString& Args, TArray<FParsedArg>& OutArgs);

View File

@@ -27,11 +27,12 @@ struct FWingProperty
explicit operator bool() const { return Prop != nullptr; } explicit operator bool() const { return Prop != nullptr; }
FProperty* operator->() const { return Prop; } FProperty* operator->() const { return Prop; }
static void Remove(TArray<FWingProperty>& Props, const FString& Name);
static TArray<FWingProperty> GetAll(UObject* Obj, EPropertyFlags Flags); static TArray<FWingProperty> GetAll(UObject* Obj, EPropertyFlags Flags);
static TArray<FWingProperty> GetAll(UStruct* StructType, void* Container, EPropertyFlags Flags); static TArray<FWingProperty> GetAll(UStruct* StructType, void* Container, EPropertyFlags Flags);
static TArray<FWingProperty> FindAllSubstring(const TArray<FWingProperty>& Props, const FString& Substring); static TArray<FWingProperty> FindAllSubstring(const TArray<FWingProperty>& Props, const FString& Substring);
static FWingProperty FindOneExactMatch(const TArray<FWingProperty>& Props, const FString& Name); static FWingProperty FindOneExactMatch(const TArray<FWingProperty>& Props, const FString& Name);
static void Remove(TArray<FWingProperty>& Props, const FString& Name);
static void Move(TArray<FWingProperty> &Out, TArray<FWingProperty> &In, const FString &Name);
private: private:
static void Collect(UStruct* Struct, void* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags); static void Collect(UStruct* Struct, void* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags);

View File

@@ -57,6 +57,7 @@ public:
static FString FormatName(const UActorComponent *C); static FString FormatName(const UActorComponent *C);
static FString FormatName(const USCS_Node *Node); static FString FormatName(const USCS_Node *Node);
static FString FormatName(const UEdGraph *Graph); static FString FormatName(const UEdGraph *Graph);
static FString FormatName(const TObjectPtr<UEdGraph> &Graph);
static FString FormatName(const UEdGraphNode* Node); static FString FormatName(const UEdGraphNode* Node);
static FString FormatName(const UEdGraphPin *Pin); static FString FormatName(const UEdGraphPin *Pin);
static FString FormatName(const FMemberReference &Ref); static FString FormatName(const FMemberReference &Ref);
@@ -80,15 +81,10 @@ public:
static FString FormatName(const FWingActorComponent &Comp); static FString FormatName(const FWingActorComponent &Comp);
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
//
// Identifies // Identifies
// //
// Return true if the name identifies the object. The // Return true if the name matches the formatted name
// FormatName functions, above, always return names that // of the object, using the formatname routines above.
// identify the object. However, there may be other
// names that also identify the object. Identifying names
// aren't 100% guaranteed to be unique, but very likely.
//
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
template<typename T> template<typename T>
@@ -97,6 +93,17 @@ public:
return FormatName(std::forward<T>(Obj)).Equals(Name, ESearchCase::IgnoreCase); return FormatName(std::forward<T>(Obj)).Equals(Name, ESearchCase::IgnoreCase);
} }
////////////////////////////////////////////////////////
//
template<typename T>
static TArray<FString> FormatNames(const TArray<T> &Array)
{
TArray<FString> Result;
for (T& Elt : Array) Result.Add(FormatName(Elt));
return Result;
}
template<typename T> template<typename T>
static TArray<T*> FindAllNamed(const FString &Name, const TArray<T*> &Array) static TArray<T*> FindAllNamed(const FString &Name, const TArray<T*> &Array)
{ {
@@ -114,41 +121,41 @@ public:
} }
template<typename T> template<typename T>
static T* FindExactlyOneNamed(const FString &Name, const TArray<T*> &Array) static T* FindExactlyOneNamed(const FString &Name, const TArray<T*> &Array, const TCHAR *Kind)
{ {
int Count = 0; int Count = 0;
T* Result = nullptr; T* Result = nullptr;
for (T* Elt : Array) if (Identifies(Name, Elt)) { Count++; Result = Elt; } for (T* Elt : Array) if (Identifies(Name, Elt)) { Count++; Result = Elt; }
if (!CheckExactlyOneNamed(Count, T::StaticClass(), Name)) return nullptr; if (!CheckExactlyOneNamed(Count, Kind, Name)) return nullptr;
return Result; return Result;
} }
template<typename T> template<typename T>
static T* FindExactlyOneNamed(const FString &Name, TArray<T> &Array) static T* FindExactlyOneNamed(const FString &Name, TArray<T> &Array, const TCHAR *Kind)
{ {
int Count = 0; int Count = 0;
T* Result = nullptr; T* Result = nullptr;
for (T& Elt : Array) if (Identifies(Name, Elt)) { Count++; Result = &Elt; } for (T& Elt : Array) if (Identifies(Name, Elt)) { Count++; Result = &Elt; }
if (!CheckExactlyOneNamed(Count, T::StaticStruct()->GetName(), Name)) return nullptr; if (!CheckExactlyOneNamed(Count, Kind, Name)) return nullptr;
return Result; return Result;
} }
template<typename T> template<typename T>
static bool FindExactlyNoneNamed(const FString &Name, const TArray<T*> &Array) static bool FindExactlyNoneNamed(const FString &Name, const TArray<T*> &Array, const TCHAR *Kind)
{ {
for (T* Elt: Array) if (Identifies(Name, Elt)) for (T* Elt: Array) if (Identifies(Name, Elt))
{ {
return CheckExactlyNoneNamed(1, T::StaticClass(), Name); return CheckExactlyNoneNamed(1, Kind, Name);
} }
return true; return true;
} }
template<typename T> template<typename T>
static bool FindExactlyNoneNamed(const FString &Name, const TArray<T> &Array) static bool FindExactlyNoneNamed(const FString &Name, const TArray<T> &Array, const TCHAR *Kind)
{ {
for (const T& Elt: Array) if (Identifies(Name, Elt)) for (const T& Elt: Array) if (Identifies(Name, Elt))
{ {
return CheckExactlyNoneNamed(1, T::StaticStruct()->GetName(), Name); return CheckExactlyNoneNamed(1, Kind, Name);
} }
return true; return true;
} }
@@ -165,20 +172,6 @@ public:
static FString SanitizeName(const FString& Name); static FString SanitizeName(const FString& Name);
static FString SanitizeName(FName Name); static FString SanitizeName(FName Name);
////////////////////////////////////////////////////////
// Our name sanitization routine, above, will turn names
// with spaces into names like "Post·Initiate·Action"
// containing middle dots instead. When the LLM creates
// new nodes, graphs, variables, or the like, it might
// suggest a name containing middle dots. In places
// like that, where the LLM is naming something new, we
// run this Unsanitize routine first. This is *not*
// used for lookups: Lookups are done by comparing
// sanitized names.
////////////////////////////////////////////////////////
static FString UnsanitizeName(const FString& Name);
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
// In Unreal, Menu items tend to be an unpredictable // In Unreal, Menu items tend to be an unpredictable
// mix of CamelCase without spaces, and with spaces. // mix of CamelCase without spaces, and with spaces.
@@ -188,6 +181,18 @@ public:
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
static FString StandardizeMenuItem(const FString &Item); static FString StandardizeMenuItem(const FString &Item);
////////////////////////////////////////////////////////
// This routine is used when the LLM is proposing a new
// name in order to create a new graph, new node, or
// something like that. This verifies that the name doesn't
// contain weird control characters that will confuse
// our name sanitization routines. If the name is valid,
// returns the *internal* name which should be stored in
// unreal's data structures. This is different than the
// name used by the LLM. If not valid, returns empty
// string.
////////////////////////////////////////////////////////
[[nodiscard]] static FString CheckProposedName(const FString &Name);
static FString FormatNodeTitle(const UEdGraphNode *Node); static FString FormatNodeTitle(const UEdGraphNode *Node);
@@ -251,10 +256,10 @@ public:
static void PrintHandlerHelp(UClass* HandlerClass); static void PrintHandlerHelp(UClass* HandlerClass);
// ----- Common Error Reporting ----- // ----- Common Error Reporting -----
static bool CheckExactlyOneNamed(int Count, const FString &Kind, const FString &Name); static bool CheckExactlyOneNamed(int Count, const TCHAR *Kind, const FString &Name);
static bool CheckExactlyOneNamed(int Count, UClass *Class, const FString &Name); static bool CheckExactlyNoneNamed(int Count, const TCHAR *Kind, const FString &Name);
static bool CheckExactlyNoneNamed(int Count, const FString &Kind, const FString &Name);
static bool CheckExactlyNoneNamed(int Count, UClass *Class, const FString &Name);
private:
static FString UnsanitizeName(const FString &Input);
}; };