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
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<UClass>(PinType.PinSubCategoryObject.Get());
check(ComponentClass);
if (!UWingComponentReference::CheckValidComponentClass(ComponentClass, WingOut::Stdout)) return;
// Find the specified parent component

View File

@@ -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<UClass>(PinType.PinSubCategoryObject.Get());
check(TypeClass);
Filter.ClassPaths.Add(TypeClass->GetClassPathName());
}

View File

@@ -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<UClass>(PinType.PinSubCategoryObject.Get());
check(InterfaceClass);
// Check for duplicates
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)

View File

@@ -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<UClass>(PinType.PinSubCategoryObject.Get());
check(FoundInterface);
// Verify this blueprint actually implements it
bool Found = false;

View File

@@ -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<UClass>(PinType.PinSubCategoryObject.Get());
check(NewParentClassObj);
// Validate reparent
if (!WingUtils::CanReparentBlueprint(BP->GeneratedClass, NewParentClassObj))
{

View File

@@ -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<UClass>(PinType.PinSubCategoryObject.Get());
}
// Check constraints.

View File

@@ -157,6 +157,21 @@ bool FWingProperty::SetText(FString Value, WingOut Errors) const
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
// 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<FEdGraphPinType>(Container);
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;
Prop->ExportTextItem_InContainer(Result, Container, nullptr, nullptr, PPF_None);
return Result;

View File

@@ -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<UWingTypes>();
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<UWingTypes>();
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<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
// ---------------------------------------------------------------------------
@@ -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")));

View File

@@ -136,9 +136,10 @@ public:
struct Requirements
{
TOptional<bool> BlueprintType;
TOptional<bool> Blueprintable;
TOptional<bool> 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