From fb5b774bfed124c24b819bec7ad024427b5457e4 Mon Sep 17 00:00:00 2001 From: jyelon Date: Sat, 4 Apr 2026 20:10:22 -0400 Subject: [PATCH] better expression of class properties --- .../UEWingman/Handlers/ActorComponent_Add.h | 8 +- .../Source/UEWingman/Handlers/Asset_Search.h | 9 +- .../Handlers/BlueprintInterface_Add.h | 9 +- .../Handlers/BlueprintInterface_Remove.h | 9 +- .../UEWingman/Handlers/Blueprint_Reparent.h | 10 +- .../UEWingman/Handlers/Create_ClassArg.h | 7 +- .../Source/UEWingman/Private/WingProperty.cpp | 21 ++++ .../Source/UEWingman/Private/WingTypes.cpp | 102 +++++++++--------- .../Source/UEWingman/Public/WingTypes.h | 17 +-- 9 files changed, 103 insertions(+), 89 deletions(-) diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Add.h b/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Add.h index 8ef263ee..4cf8bc51 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Add.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Add.h @@ -58,12 +58,12 @@ public: // Resolve the component class by name UWingTypes::Requirements Req; - Req.BlueprintType = false; - Req.Blueprintable = false; Req.AllowContainer = false; Req.IsChildOf = UActorComponent::StaticClass(); - UClass* ComponentClass = UWingTypes::TextToOneObjectType(Class, Req, WingOut::Stdout); - if (!ComponentClass) return; + FEdGraphPinType PinType; + if (!UWingTypes::TextToType(Class, PinType, Req, WingOut::Stdout)) return; + UClass* ComponentClass = Cast(PinType.PinSubCategoryObject.Get()); + check(ComponentClass); if (!UWingComponentReference::CheckValidComponentClass(ComponentClass, WingOut::Stdout)) return; // Find the specified parent component diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Asset_Search.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Asset_Search.h index 6dab076a..897bd894 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Asset_Search.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Asset_Search.h @@ -52,11 +52,12 @@ public: if (!Type.IsEmpty()) { UWingTypes::Requirements Req; - Req.BlueprintType = false; - Req.Blueprintable = false; Req.AllowContainer = false; - UClass* TypeClass = UWingTypes::TextToOneObjectType(Type, Req, WingOut::Stdout); - if (!TypeClass) return; + Req.IsChildOf = UObject::StaticClass(); + FEdGraphPinType PinType; + if (!UWingTypes::TextToType(Type, PinType, Req, WingOut::Stdout)) return; + UClass* TypeClass = Cast(PinType.PinSubCategoryObject.Get()); + check(TypeClass); Filter.ClassPaths.Add(TypeClass->GetClassPathName()); } diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintInterface_Add.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintInterface_Add.h index 10aede51..713cc234 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintInterface_Add.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintInterface_Add.h @@ -40,11 +40,12 @@ public: // Resolve the interface class UWingTypes::Requirements Req; - Req.BlueprintType = false; - Req.Blueprintable = false; Req.AllowContainer = false; - UClass* InterfaceClass = UWingTypes::TextToOneInterfaceType(Interface, Req, WingOut::Stdout); - if (!InterfaceClass) return; + Req.IsChildOf = UInterface::StaticClass(); + FEdGraphPinType PinType; + if (!UWingTypes::TextToType(Interface, PinType, Req, WingOut::Stdout)) return; + UClass* InterfaceClass = Cast(PinType.PinSubCategoryObject.Get()); + check(InterfaceClass); // Check for duplicates for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces) diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintInterface_Remove.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintInterface_Remove.h index cc6b4b25..e1f502d8 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintInterface_Remove.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintInterface_Remove.h @@ -44,11 +44,12 @@ public: // Resolve the interface name to a UClass* UWingTypes::Requirements Req; - Req.BlueprintType = false; - Req.Blueprintable = false; Req.AllowContainer = false; - UClass* FoundInterface = UWingTypes::TextToOneInterfaceType(Interface, Req, WingOut::Stdout); - if (!FoundInterface) return; + Req.IsChildOf = UInterface::StaticClass(); + FEdGraphPinType PinType; + if (!UWingTypes::TextToType(Interface, PinType, Req, WingOut::Stdout)) return; + UClass* FoundInterface = Cast(PinType.PinSubCategoryObject.Get()); + check(FoundInterface); // Verify this blueprint actually implements it bool Found = false; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Reparent.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Reparent.h index 32b6b5ca..7d5a4e01 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Reparent.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Reparent.h @@ -42,12 +42,14 @@ public: // Find the new parent class by short type name UWingTypes::Requirements Req; - Req.BlueprintType = false; Req.Blueprintable = true; Req.AllowContainer = false; - UClass* NewParentClassObj = UWingTypes::TextToOneObjectType(Parent, Req, WingOut::Stdout); - if (!NewParentClassObj) return; - + Req.IsChildOf = UObject::StaticClass(); + FEdGraphPinType PinType; + if (!UWingTypes::TextToType(Parent, PinType, Req, WingOut::Stdout)) return; + UClass* NewParentClassObj = Cast(PinType.PinSubCategoryObject.Get()); + check(NewParentClassObj); + // Validate reparent if (!WingUtils::CanReparentBlueprint(BP->GeneratedClass, NewParentClassObj)) { diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Create_ClassArg.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Create_ClassArg.h index 97ec1f42..ae9d9079 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Create_ClassArg.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Create_ClassArg.h @@ -213,12 +213,13 @@ public: if (!Class.IsEmpty()) { UWingTypes::Requirements Req; - Req.BlueprintType = false; Req.Blueprintable = Config.Blueprintable; Req.AllowContainer = false; + Req.AllowNone = !Config.ValidPointer; Req.IsChildOf = ConfObj->BaseClass; - ClassObj = UWingTypes::TextToOneObjectType(Class, Req, WingOut::Stdout); - if (!ClassObj) return; + FEdGraphPinType PinType; + if (!UWingTypes::TextToType(Class, PinType, Req, WingOut::Stdout)) return; + ClassObj = Cast(PinType.PinSubCategoryObject.Get()); } // Check constraints. diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp index d6d61515..29b180a7 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp @@ -157,6 +157,21 @@ bool FWingProperty::SetText(FString Value, WingOut Errors) const return true; } + // Class properties get parsed by UWingTypes. + if (FClassProperty *CProp = CastField(Prop)) + { + UWingTypes::Requirements Req; + Req.IsChildOf = CProp->MetaClass; + if (CProp->MetaClass == nullptr) Req.IsChildOf = UObject::StaticClass(); + Req.AllowNone = true; + Req.AllowContainer = false; + FEdGraphPinType PinType; + if (!UWingTypes::TextToType(Value, PinType, Req, Errors)) return false; + CProp->SetObjectPropertyValue_InContainer(Container, + Cast(PinType.PinSubCategoryObject.Get())); + return true; + } + // If it's an enum type, use our parsing routine which is smarter about // prefixes than ImportText. We canonicalize the string, and then send // it onward to ImportText. @@ -322,6 +337,12 @@ FString FWingProperty::GetText() const FEdGraphPinType *PinType = Prop->ContainerPtrToValuePtr(Container); return UWingTypes::TypeToText(*PinType); } + if (FClassProperty *CProp = CastField(Prop)) + { + UObject *Obj = CProp->GetObjectPropertyValue_InContainer(Container); + if (Obj) return UWingTypes::TypeToText(Obj); + return TEXT("None"); + } FString Result; Prop->ExportTextItem_InContainer(Result, Container, nullptr, nullptr, PPF_None); return Result; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp index 4f569469..84de72e6 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp @@ -14,6 +14,15 @@ #include "Kismet2/KismetEditorUtilities.h" +static const FName NAME_TypeArray(TEXT("Array")); +static const FName NAME_TypeSet(TEXT("Set")); +static const FName NAME_TypeMap(TEXT("Map")); +static const FName NAME_TypeSoft(TEXT("Soft")); +static const FName NAME_TypeClass(TEXT("Class")); +static const FName NAME_TypeSoftClass(TEXT("SoftClass")); +static const FName NAME_StartOfType(TEXT("Start-of-Type")); +static const FName NAME_NoneMeaningNullptr(TEXT("None")); + // --------------------------------------------------------------------------- // Simple Accessors // --------------------------------------------------------------------------- @@ -318,7 +327,8 @@ FString UWingTypes::TypeToText(FName Category, FName SubCategory, UObject* SubCa (Category == UEdGraphSchema_K2::PC_Int64) || (Category == UEdGraphSchema_K2::PC_Name) || (Category == UEdGraphSchema_K2::PC_String) || - (Category == UEdGraphSchema_K2::PC_Text)) + (Category == UEdGraphSchema_K2::PC_Text) || + (Category == NAME_NoneMeaningNullptr)) { return Category.ToString(); } @@ -408,6 +418,7 @@ FString UWingTypes::TypeToText(const UObject* Obj) { UWingTypes* Types = GEditor->GetEditorSubsystem(); if (!Types) return FString(); + if (Obj == nullptr) return TEXT("None"); return Types->ChooseShortName(Obj); } @@ -415,6 +426,7 @@ FString UWingTypes::TypeToTextOrDie(const UObject* Obj) { UWingTypes* Types = GEditor->GetEditorSubsystem(); if (!Types) return FString(); + if (Obj == nullptr) return TEXT("None"); FString Result = Types->ChooseShortName(Obj); check(!Result.IsEmpty()); return Result; @@ -424,15 +436,6 @@ FString UWingTypes::TypeToTextOrDie(const UObject* Obj) // Parser and Resolve Short Name // --------------------------------------------------------------------------- -static const FName NAME_TypeArray(TEXT("Array")); -static const FName NAME_TypeSet(TEXT("Set")); -static const FName NAME_TypeMap(TEXT("Map")); -static const FName NAME_TypeSoft(TEXT("Soft")); -static const FName NAME_TypeClass(TEXT("Class")); -static const FName NAME_TypeSoftClass(TEXT("SoftClass")); - -static const FName NAME_StartOfType("Start-of-Type"); - void UWingTypes::PrintParseError(WingTokenizer& Tok, const TCHAR* Message, WingOut Errors) { FString TypeText(Tok.GetRange(NAME_StartOfType, 1)); @@ -611,14 +614,6 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con check(Types); Tok.SaveCursor(NAME_StartOfType); - if (!Require.BlueprintType.IsSet() || - !Require.Blueprintable.IsSet() || - !Require.AllowContainer.IsSet()) - { - Errors.Printf(TEXT("ERROR: TextToType called with underspecified Requirements list.\n")); - return false; - } - OutPinType = FEdGraphPinType(); if (!Types->ParseType(Tok, OutPinType, Errors)) { @@ -631,7 +626,28 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con OutPinType = FEdGraphPinType(); return false; } - if (!Require.AllowContainer.GetValue()) + // Check for none. If the type is none, and allownone is specified, + // then that overrides a lot of other requirements. We set a flag for this. + bool IsNone = OutPinType.PinCategory == NAME_NoneMeaningNullptr; + bool AllowNoneIsOverriding = (Require.AllowNone && IsNone); + + // None is never allowed inside a container. + if (IsNone && OutPinType.IsContainer()) + { + Errors.Printf(TEXT("ERROR: 'None' is not allowed in an array/set/map\n")); + UWingServer::SuggestManual(WingManual::Section::HandlerHelp); + OutPinType = FEdGraphPinType(); return false; + } + + // Don't allow none unless AllowNone is set. + if (!Require.AllowNone && IsNone) + { + Errors.Printf(TEXT("ERROR: 'None' is not allowed here\n")); + UWingServer::SuggestManual(WingManual::Section::HandlerHelp); + OutPinType = FEdGraphPinType(); return false; + } + + if (!Require.AllowContainer) { if (OutPinType.IsContainer()) { @@ -642,7 +658,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con } } - if (!Require.PinCategory.IsNone()) + if (!Require.PinCategory.IsNone() && (!AllowNoneIsOverriding)) { if (OutPinType.PinCategory != Require.PinCategory) { @@ -653,8 +669,17 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con } } - if (Require.IsChildOf) + if (Require.IsChildOf && (!AllowNoneIsOverriding)) { + // If the base class is not an interface, don't allow interfaces. + if (!Require.IsChildOf->IsChildOf(UInterface::StaticClass()) + && OutPinType.PinCategory == UEdGraphSchema_K2::PC_Interface) + { + FString Text(Tok.GetRange(NAME_StartOfType, 0)); + Errors.Printf(TEXT("ERROR: '%s' is an interface, not a class\n"), *Text); + UWingServer::SuggestManual(WingManual::Section::HandlerHelp); + OutPinType = FEdGraphPinType(); return false; + } if (!IsChildOf(OutPinType, Require.IsChildOf)) { Errors.Printf(TEXT("ERROR: Type must derive from %s\n"), *WingUtils::FormatName(Require.IsChildOf)); @@ -663,7 +688,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con } } - if (Require.BlueprintType.GetValue()) + if (Require.BlueprintType && (!AllowNoneIsOverriding)) { if (!IsBlueprintType(OutPinType)) { @@ -674,7 +699,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con } } - if (Require.Blueprintable.GetValue()) + if (Require.Blueprintable && (!AllowNoneIsOverriding)) { if (!IsBlueprintable(OutPinType)) { @@ -694,36 +719,6 @@ bool UWingTypes::TextToType(const FString &Text, FEdGraphPinType& OutPinType, co return TextToType(Tok, OutPinType, Require, true, Errors); } -UClass* UWingTypes::TextToOneObjectType(const FString& Text, const Requirements &Require, WingOut Errors) -{ - FEdGraphPinType PinType; - if (!TextToType(Text, PinType, Require, Errors)) return nullptr; - UClass* Class = Cast(PinType.PinSubCategoryObject.Get()); - if ((!Class) || (PinType.PinCategory != UEdGraphSchema_K2::PC_Object) || - (PinType.IsContainer())) - { - Errors.Printf(TEXT("ERROR: '%s' is not an object class\n"), *Text); - UWingServer::SuggestManual(WingManual::Section::Types); - return nullptr; - } - return Class; -} - -UClass* UWingTypes::TextToOneInterfaceType(const FString& Text, const Requirements &Require, WingOut Errors) -{ - FEdGraphPinType PinType; - if (!TextToType(Text, PinType, Require, Errors)) return nullptr; - UClass* Class = Cast(PinType.PinSubCategoryObject.Get()); - if ((!Class) || (PinType.PinCategory != UEdGraphSchema_K2::PC_Interface) || - (PinType.IsContainer())) - { - Errors.Printf(TEXT("ERROR: '%s' is not an interface class\n"), *Text); - UWingServer::SuggestManual(WingManual::Section::Types); - return nullptr; - } - return Class; -} - // --------------------------------------------------------------------------- // Subsystem lifecycle // --------------------------------------------------------------------------- @@ -750,6 +745,7 @@ void UWingTypes::Initialize(FSubsystemCollectionBase& Collection) ReserveShortName(UEdGraphSchema_K2::PC_Name); ReserveShortName(UEdGraphSchema_K2::PC_String); ReserveShortName(UEdGraphSchema_K2::PC_Text); + ReserveShortName(NAME_NoneMeaningNullptr); // Scan priority packages first, then everything else in sorted order. ChooseShortNames(FindPackage(nullptr, TEXT("/Script/CoreUObject"))); diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingTypes.h b/Plugins/UEWingman/Source/UEWingman/Public/WingTypes.h index 4144384d..9b39de81 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingTypes.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingTypes.h @@ -136,9 +136,10 @@ public: struct Requirements { - TOptional BlueprintType; - TOptional Blueprintable; - TOptional AllowContainer; + bool BlueprintType = false; + bool Blueprintable = false; + bool AllowContainer = false; + bool AllowNone = false; UClass *IsChildOf = nullptr; FName PinCategory = FName(); }; @@ -153,16 +154,6 @@ public: // requirements, prints an error and returns false. static bool TextToType(const FString& Text, FEdGraphPinType& OutPinType, const Requirements &Require, WingOut Errors); - // Parse a type. If it doesn't parse, or if the type doesn't satisfy the - // requirements, prints an error and returns false. Requires that the type - // is an object type (even if that's not specified in the requirements struct). - static UClass* TextToOneObjectType(const FString& Text, const Requirements &Require, WingOut Errors); - - // Parse a type. If it doesn't parse, or if the type doesn't satisfy the - // requirements, prints an error and returns false. Requires that the type - // is an interface type (even if that's not specified in the requirements struct). - static UClass* TextToOneInterfaceType(const FString& Text, const Requirements &Require, WingOut Errors); - private: // --------------------------------------------------------------------------- // Subsystem lifecycle