better expression of class properties

This commit is contained in:
2026-04-04 20:10:22 -04:00
parent 47acf1aca4
commit fb5b774bfe
9 changed files with 103 additions and 89 deletions

View File

@@ -58,12 +58,12 @@ public:
// Resolve the component class by name // Resolve the component class by name
UWingTypes::Requirements Req; UWingTypes::Requirements Req;
Req.BlueprintType = false;
Req.Blueprintable = false;
Req.AllowContainer = false; Req.AllowContainer = false;
Req.IsChildOf = UActorComponent::StaticClass(); Req.IsChildOf = UActorComponent::StaticClass();
UClass* ComponentClass = UWingTypes::TextToOneObjectType(Class, Req, WingOut::Stdout); FEdGraphPinType PinType;
if (!ComponentClass) return; if (!UWingTypes::TextToType(Class, PinType, Req, WingOut::Stdout)) return;
UClass* ComponentClass = Cast<UClass>(PinType.PinSubCategoryObject.Get());
check(ComponentClass);
if (!UWingComponentReference::CheckValidComponentClass(ComponentClass, WingOut::Stdout)) return; if (!UWingComponentReference::CheckValidComponentClass(ComponentClass, WingOut::Stdout)) return;
// Find the specified parent component // Find the specified parent component

View File

@@ -52,11 +52,12 @@ public:
if (!Type.IsEmpty()) if (!Type.IsEmpty())
{ {
UWingTypes::Requirements Req; UWingTypes::Requirements Req;
Req.BlueprintType = false;
Req.Blueprintable = false;
Req.AllowContainer = false; Req.AllowContainer = false;
UClass* TypeClass = UWingTypes::TextToOneObjectType(Type, Req, WingOut::Stdout); Req.IsChildOf = UObject::StaticClass();
if (!TypeClass) return; FEdGraphPinType PinType;
if (!UWingTypes::TextToType(Type, PinType, Req, WingOut::Stdout)) return;
UClass* TypeClass = Cast<UClass>(PinType.PinSubCategoryObject.Get());
check(TypeClass);
Filter.ClassPaths.Add(TypeClass->GetClassPathName()); Filter.ClassPaths.Add(TypeClass->GetClassPathName());
} }

View File

@@ -40,11 +40,12 @@ public:
// Resolve the interface class // Resolve the interface class
UWingTypes::Requirements Req; UWingTypes::Requirements Req;
Req.BlueprintType = false;
Req.Blueprintable = false;
Req.AllowContainer = false; Req.AllowContainer = false;
UClass* InterfaceClass = UWingTypes::TextToOneInterfaceType(Interface, Req, WingOut::Stdout); Req.IsChildOf = UInterface::StaticClass();
if (!InterfaceClass) return; FEdGraphPinType PinType;
if (!UWingTypes::TextToType(Interface, PinType, Req, WingOut::Stdout)) return;
UClass* InterfaceClass = Cast<UClass>(PinType.PinSubCategoryObject.Get());
check(InterfaceClass);
// Check for duplicates // Check for duplicates
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces) for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)

View File

@@ -44,11 +44,12 @@ public:
// Resolve the interface name to a UClass* // Resolve the interface name to a UClass*
UWingTypes::Requirements Req; UWingTypes::Requirements Req;
Req.BlueprintType = false;
Req.Blueprintable = false;
Req.AllowContainer = false; Req.AllowContainer = false;
UClass* FoundInterface = UWingTypes::TextToOneInterfaceType(Interface, Req, WingOut::Stdout); Req.IsChildOf = UInterface::StaticClass();
if (!FoundInterface) return; FEdGraphPinType PinType;
if (!UWingTypes::TextToType(Interface, PinType, Req, WingOut::Stdout)) return;
UClass* FoundInterface = Cast<UClass>(PinType.PinSubCategoryObject.Get());
check(FoundInterface);
// Verify this blueprint actually implements it // Verify this blueprint actually implements it
bool Found = false; bool Found = false;

View File

@@ -42,11 +42,13 @@ public:
// Find the new parent class by short type name // Find the new parent class by short type name
UWingTypes::Requirements Req; UWingTypes::Requirements Req;
Req.BlueprintType = false;
Req.Blueprintable = true; Req.Blueprintable = true;
Req.AllowContainer = false; Req.AllowContainer = false;
UClass* NewParentClassObj = UWingTypes::TextToOneObjectType(Parent, Req, WingOut::Stdout); Req.IsChildOf = UObject::StaticClass();
if (!NewParentClassObj) return; FEdGraphPinType PinType;
if (!UWingTypes::TextToType(Parent, PinType, Req, WingOut::Stdout)) return;
UClass* NewParentClassObj = Cast<UClass>(PinType.PinSubCategoryObject.Get());
check(NewParentClassObj);
// Validate reparent // Validate reparent
if (!WingUtils::CanReparentBlueprint(BP->GeneratedClass, NewParentClassObj)) if (!WingUtils::CanReparentBlueprint(BP->GeneratedClass, NewParentClassObj))

View File

@@ -213,12 +213,13 @@ public:
if (!Class.IsEmpty()) if (!Class.IsEmpty())
{ {
UWingTypes::Requirements Req; UWingTypes::Requirements Req;
Req.BlueprintType = false;
Req.Blueprintable = Config.Blueprintable; Req.Blueprintable = Config.Blueprintable;
Req.AllowContainer = false; Req.AllowContainer = false;
Req.AllowNone = !Config.ValidPointer;
Req.IsChildOf = ConfObj->BaseClass; Req.IsChildOf = ConfObj->BaseClass;
ClassObj = UWingTypes::TextToOneObjectType(Class, Req, WingOut::Stdout); FEdGraphPinType PinType;
if (!ClassObj) return; if (!UWingTypes::TextToType(Class, PinType, Req, WingOut::Stdout)) return;
ClassObj = Cast<UClass>(PinType.PinSubCategoryObject.Get());
} }
// Check constraints. // Check constraints.

View File

@@ -157,6 +157,21 @@ bool FWingProperty::SetText(FString Value, WingOut Errors) const
return true; return true;
} }
// Class properties get parsed by UWingTypes.
if (FClassProperty *CProp = CastField<FClassProperty>(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<UClass>(PinType.PinSubCategoryObject.Get()));
return true;
}
// If it's an enum type, use our parsing routine which is smarter about // If it's an enum type, use our parsing routine which is smarter about
// prefixes than ImportText. We canonicalize the string, and then send // prefixes than ImportText. We canonicalize the string, and then send
// it onward to ImportText. // it onward to ImportText.
@@ -322,6 +337,12 @@ FString FWingProperty::GetText() const
FEdGraphPinType *PinType = Prop->ContainerPtrToValuePtr<FEdGraphPinType>(Container); FEdGraphPinType *PinType = Prop->ContainerPtrToValuePtr<FEdGraphPinType>(Container);
return UWingTypes::TypeToText(*PinType); return UWingTypes::TypeToText(*PinType);
} }
if (FClassProperty *CProp = CastField<FClassProperty>(Prop))
{
UObject *Obj = CProp->GetObjectPropertyValue_InContainer(Container);
if (Obj) return UWingTypes::TypeToText(Obj);
return TEXT("None");
}
FString Result; FString Result;
Prop->ExportTextItem_InContainer(Result, Container, nullptr, nullptr, PPF_None); Prop->ExportTextItem_InContainer(Result, Container, nullptr, nullptr, PPF_None);
return Result; return Result;

View File

@@ -14,6 +14,15 @@
#include "Kismet2/KismetEditorUtilities.h" #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 // Simple Accessors
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -318,7 +327,8 @@ FString UWingTypes::TypeToText(FName Category, FName SubCategory, UObject* SubCa
(Category == UEdGraphSchema_K2::PC_Int64) || (Category == UEdGraphSchema_K2::PC_Int64) ||
(Category == UEdGraphSchema_K2::PC_Name) || (Category == UEdGraphSchema_K2::PC_Name) ||
(Category == UEdGraphSchema_K2::PC_String) || (Category == UEdGraphSchema_K2::PC_String) ||
(Category == UEdGraphSchema_K2::PC_Text)) (Category == UEdGraphSchema_K2::PC_Text) ||
(Category == NAME_NoneMeaningNullptr))
{ {
return Category.ToString(); return Category.ToString();
} }
@@ -408,6 +418,7 @@ FString UWingTypes::TypeToText(const UObject* Obj)
{ {
UWingTypes* Types = GEditor->GetEditorSubsystem<UWingTypes>(); UWingTypes* Types = GEditor->GetEditorSubsystem<UWingTypes>();
if (!Types) return FString(); if (!Types) return FString();
if (Obj == nullptr) return TEXT("None");
return Types->ChooseShortName(Obj); return Types->ChooseShortName(Obj);
} }
@@ -415,6 +426,7 @@ FString UWingTypes::TypeToTextOrDie(const UObject* Obj)
{ {
UWingTypes* Types = GEditor->GetEditorSubsystem<UWingTypes>(); UWingTypes* Types = GEditor->GetEditorSubsystem<UWingTypes>();
if (!Types) return FString(); if (!Types) return FString();
if (Obj == nullptr) return TEXT("None");
FString Result = Types->ChooseShortName(Obj); FString Result = Types->ChooseShortName(Obj);
check(!Result.IsEmpty()); check(!Result.IsEmpty());
return Result; return Result;
@@ -424,15 +436,6 @@ FString UWingTypes::TypeToTextOrDie(const UObject* Obj)
// Parser and Resolve Short Name // 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) void UWingTypes::PrintParseError(WingTokenizer& Tok, const TCHAR* Message, WingOut Errors)
{ {
FString TypeText(Tok.GetRange(NAME_StartOfType, 1)); FString TypeText(Tok.GetRange(NAME_StartOfType, 1));
@@ -611,14 +614,6 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
check(Types); check(Types);
Tok.SaveCursor(NAME_StartOfType); 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(); OutPinType = FEdGraphPinType();
if (!Types->ParseType(Tok, OutPinType, Errors)) if (!Types->ParseType(Tok, OutPinType, Errors))
{ {
@@ -631,7 +626,28 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
OutPinType = FEdGraphPinType(); return false; 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()) 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) 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)) if (!IsChildOf(OutPinType, Require.IsChildOf))
{ {
Errors.Printf(TEXT("ERROR: Type must derive from %s\n"), *WingUtils::FormatName(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)) 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)) if (!IsBlueprintable(OutPinType))
{ {
@@ -694,36 +719,6 @@ bool UWingTypes::TextToType(const FString &Text, FEdGraphPinType& OutPinType, co
return TextToType(Tok, OutPinType, Require, true, Errors); 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<UClass>(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<UClass>(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 // Subsystem lifecycle
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -750,6 +745,7 @@ void UWingTypes::Initialize(FSubsystemCollectionBase& Collection)
ReserveShortName(UEdGraphSchema_K2::PC_Name); ReserveShortName(UEdGraphSchema_K2::PC_Name);
ReserveShortName(UEdGraphSchema_K2::PC_String); ReserveShortName(UEdGraphSchema_K2::PC_String);
ReserveShortName(UEdGraphSchema_K2::PC_Text); ReserveShortName(UEdGraphSchema_K2::PC_Text);
ReserveShortName(NAME_NoneMeaningNullptr);
// Scan priority packages first, then everything else in sorted order. // Scan priority packages first, then everything else in sorted order.
ChooseShortNames(FindPackage(nullptr, TEXT("/Script/CoreUObject"))); ChooseShortNames(FindPackage(nullptr, TEXT("/Script/CoreUObject")));

View File

@@ -136,9 +136,10 @@ public:
struct Requirements struct Requirements
{ {
TOptional<bool> BlueprintType; bool BlueprintType = false;
TOptional<bool> Blueprintable; bool Blueprintable = false;
TOptional<bool> AllowContainer; bool AllowContainer = false;
bool AllowNone = false;
UClass *IsChildOf = nullptr; UClass *IsChildOf = nullptr;
FName PinCategory = FName(); FName PinCategory = FName();
}; };
@@ -153,16 +154,6 @@ public:
// 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, WingOut Errors); 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: private:
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Subsystem lifecycle // Subsystem lifecycle