From 92e41c857acf3ddfa05af6c43c1a7a0390d07b87 Mon Sep 17 00:00:00 2001 From: jyelon Date: Sat, 4 Apr 2026 05:22:08 -0400 Subject: [PATCH] Lots of refactoring --- .../UEWingman/Handlers/Create_Blueprint.h | 20 +- .../UEWingman/Handlers/Create_UsingFactory.h | 12 +- .../Source/UEWingman/Handlers/Details_Dump.h | 10 +- .../Handlers/Documentation_CreateAssets.h | 2 +- .../UEWingman/Handlers/SysInfo_Factories.h | 42 +- .../UEWingman/Private/WingFactories.cpp | 19 +- .../UEWingman/Private/WingGraphExport.cpp | 19 +- .../UEWingman/Private/WingPropHandle.cpp | 524 ------------------ .../Source/UEWingman/Private/WingProperty.cpp | 10 + .../Source/UEWingman/Private/WingServer.cpp | 12 + .../Source/UEWingman/Public/WingFactories.h | 12 +- .../Source/UEWingman/Public/WingGraphExport.h | 5 +- .../Source/UEWingman/Public/WingPropHandle.h | 103 ---- .../Source/UEWingman/Public/WingProperty.h | 4 + .../Source/UEWingman/Public/WingServer.h | 2 + 15 files changed, 105 insertions(+), 691 deletions(-) delete mode 100644 Plugins/UEWingman/Source/UEWingman/Private/WingPropHandle.cpp delete mode 100644 Plugins/UEWingman/Source/UEWingman/Public/WingPropHandle.h diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Create_Blueprint.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Create_Blueprint.h index 905faf94..6c097e54 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Create_Blueprint.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Create_Blueprint.h @@ -5,7 +5,7 @@ #include "WingServer.h" #include "WingHandler.h" #include "WingFactories.h" -#include "WingPropHandle.h" +#include "WingProperty.h" #include "WingTypes.h" #include "Factories/BlueprintFactory.h" #include "Factories/BlueprintMacroFactory.h" @@ -80,18 +80,12 @@ public: return; } - // Get the 'ParentClass' property. - WingPropHandle Props; - TSharedPtr PCProp = Props.NamedProperty(Factory, TEXT("ParentClass"), true, WingOut::Stdout); - if (!PCProp) return; - - // Store the parent class. - FPropertyAccess::Result SetResult = PCProp->SetValue(ParentClassObj); - if (SetResult != FPropertyAccess::Result::Success) - { - WingOut::Stdout.Printf(TEXT("ERROR: property does not allow value: %s\n"), *ParentClass); - return; - } + // Set the 'ParentClass' property. + TArray Props = FWingProperty::GetAll(Factory, CPF_Edit); + FWingProperty *Prop = WingUtils::FindOneWithInternalID( + TEXT("ParentClass"), Props, TEXT("property"), WingOut::Stdout); + if (!Prop) return; + if (!Prop->SetObject(ParentClassObj, WingOut::Stdout)) return; // Create the asset using the factory. UObject *Blueprint = WingFactories::CreateAsset(Path, Factory, WingOut::Stdout); diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Create_UsingFactory.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Create_UsingFactory.h index 816384a4..d956bfa9 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Create_UsingFactory.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Create_UsingFactory.h @@ -5,7 +5,6 @@ #include "WingServer.h" #include "WingHandler.h" #include "WingFactories.h" -#include "WingPropHandle.h" #include "Factories/BlueprintFunctionLibraryFactory.h" #include "Create_UsingFactory.generated.h" @@ -22,17 +21,10 @@ public: { TArray FactoryClasses; GetDerivedClasses(UFactory::StaticClass(), FactoryClasses); - for (UClass* Class : FactoryClasses) { - if (Class->HasAnyClassFlags(CLASS_Abstract)) continue; - - UFactory* CDO = Class->GetDefaultObject(); - if (!CDO->CanCreateNew() || !CDO->ShouldShowInNewMenu()) continue; - - WingPropHandle Props; - TArray> ConfigProps = Props.AllProperties(CDO, true); - if (ConfigProps.Num() > 0) continue; + if (!WingFactories::CanCreate(Class)) continue; + if (WingFactories::GetParameterNames(Class).Num() > 0) continue; FString FactoryName = WingFactories::DeriveFactoryName(Class); FString CommandName = FString::Printf(TEXT("Create_%s"), *FactoryName); diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Details_Dump.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Details_Dump.h index 4b8428c7..4abcdb80 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Details_Dump.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Details_Dump.h @@ -6,7 +6,6 @@ #include "WingFetcher.h" #include "WingProperty.h" #include "WingUtils.h" -#include "WingTypes.h" #include "Details_Dump.generated.h" UCLASS() @@ -45,14 +44,7 @@ public: { WingOut::Stdout.Printf(TEXT("\n%s:\n"), *Pair.Key); for (const FWingProperty& P : Pair.Value) - { - bool bEditable = !P->HasAnyPropertyFlags(CPF_EditConst); - WingOut::Stdout.Printf(TEXT(" %s %s %s = %s\n"), - bEditable ? TEXT("editable") : TEXT("readonly"), - *UWingTypes::TypeToText(P.Prop), - *WingUtils::FormatName(P.Prop), - *P.GetTruncatedText(100)); - } + P.Print(WingOut::Stdout); } } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Documentation_CreateAssets.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Documentation_CreateAssets.h index 5950ee9f..fef86c5b 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Documentation_CreateAssets.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Documentation_CreateAssets.h @@ -28,6 +28,6 @@ public: } virtual void Handle() override { - WingManual::Commands(EWingHandlerKind::Normal, Query, Verbose); + WingManual::Commands(EWingHandlerKind::Create, Query, Verbose); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/SysInfo_Factories.h b/Plugins/UEWingman/Source/UEWingman/Handlers/SysInfo_Factories.h index c9caabe2..36c8a03e 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/SysInfo_Factories.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/SysInfo_Factories.h @@ -3,7 +3,6 @@ #include "CoreMinimal.h" #include "WingServer.h" #include "WingHandler.h" -#include "WingProperty.h" #include "WingFactories.h" #include "SysInfo_Factories.generated.h" @@ -28,30 +27,45 @@ public: virtual void Handle() override { + // Build a set of factory classes that are already supported by Create commands. + TSet SupportedFactories; + for (const FWingHandlerConfig& H : UWingServer::AllHandlers()) + { + if (H.Kind != EWingHandlerKind::Create) continue; + UClass* FactoryClass = Cast(H.Config.Get()); + if (FactoryClass && FactoryClass->IsChildOf(UFactory::StaticClass())) + SupportedFactories.Add(FactoryClass); + } + TArray FactoryClasses; GetDerivedClasses(UFactory::StaticClass(), FactoryClasses); - TArray Results; + TArray Supported; + TArray Unsupported; for (UClass *Factory : FactoryClasses) { - if (Factory->HasAnyClassFlags(CLASS_Abstract)) continue; - UFactory *CDO = Factory->GetDefaultObject(); - if (!CDO->CanCreateNew()) continue; - if (!CDO->ShouldShowInNewMenu()) continue; - TArray Params = FWingProperty::GetNames(Factory, CPF_Edit); + if (!WingFactories::CanCreate(Factory)) continue; + TArray Params = WingFactories::GetParameterNames(Factory); TStringBuilder<512> Line; Line.Appendf(TEXT("%2d %s "), Params.Num(), *Factory->GetName()); for (const FName& Prop : Params) - { Line.Appendf(TEXT(" %s"), *Prop.ToString()); - } - Results.Add(Line.ToString()); + if (SupportedFactories.Contains(Factory)) + Supported.Add(Line.ToString()); + else + Unsupported.Add(Line.ToString()); } - Results.Sort(); - for (const FString &Line : Results) - { + + Supported.Sort(); + Unsupported.Sort(); + + WingOut::Stdout.Print(TEXT("SUPPORTED:\n")); + for (const FString &Line : Supported) + WingOut::Stdout.Printf(TEXT("%s\n"), *Line); + + WingOut::Stdout.Print(TEXT("\nUNSUPPORTED:\n")); + for (const FString &Line : Unsupported) WingOut::Stdout.Printf(TEXT("%s\n"), *Line); - } } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingFactories.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingFactories.cpp index 1cbf035f..6a3f3405 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingFactories.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingFactories.cpp @@ -2,6 +2,7 @@ #include "WingServer.h" #include "WingUtils.h" #include "PackageTools.h" +#include "WingProperty.h" #include "AssetRegistry/AssetRegistryModule.h" #include "Kismet2/EnumEditorUtils.h" @@ -30,7 +31,7 @@ bool WingFactories::CheckNewAssetPath(const FString& Path, WingOut Errors) return true; } -bool WingFactories::IsBlacklisted(UClass* FactoryClass) +bool WingFactories::IsBlacklisted(TSubclassOf FactoryClass) { FName Name = FactoryClass->GetFName(); if (Name == TEXT("PhysicsAssetFactory")) return true; // Pops a modal dialog @@ -38,6 +39,20 @@ bool WingFactories::IsBlacklisted(UClass* FactoryClass) return false; } +bool WingFactories::CanCreate(TSubclassOf FactoryClass) +{ + if (FactoryClass == nullptr) return false; + if (FactoryClass->HasAnyClassFlags(CLASS_Abstract)) return false; + if (IsBlacklisted(FactoryClass)) return false; + UFactory* CDO = FactoryClass->GetDefaultObject(); + return CDO->CanCreateNew() && CDO->ShouldShowInNewMenu(); +} + +TArray WingFactories::GetParameterNames(TSubclassOf FactoryClass) +{ + return FWingProperty::GetNames(FactoryClass, CPF_Edit); +} + UObject* WingFactories::CreateAsset(const FString& Path, UFactory* Factory, WingOut Errors) { // Check the blacklist. @@ -90,7 +105,7 @@ UObject* WingFactories::CreateAsset(const FString& Path, UFactory* Factory, Wing return NewAsset; } -FString WingFactories::DeriveFactoryName(UClass* FactoryClass) +FString WingFactories::DeriveFactoryName(TSubclassOf FactoryClass) { FString Name = FactoryClass->GetName(); int32 Index = Name.Find(TEXT("Factory")); diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp index 0622acc3..ae1a8c2e 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp @@ -204,7 +204,7 @@ void WingGraphExport::EmitNode(UEdGraphNode* Node) Output.Appendf(TEXT("\nnode %s: %s\n"), *WingUtils::FormatName(Node), *WingUtils::FormatNodeTitle(Node)); // Emit node properties (if applicable). - EmitNodeProperties(Node, Output, true); + EmitNodeProperties(Node, &Output, true); // Emit input data pins. for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Input)) @@ -248,26 +248,25 @@ void WingGraphExport::EmitNode(UEdGraphNode* Node) } } -void WingGraphExport::EmitNodeProperties(UEdGraphNode* Node, FStringBuilderBase& Out, bool bPrimary) +void WingGraphExport::EmitNodeProperties(UEdGraphNode* Node, WingOut Out, bool bPrimary) { + TArray Props = FWingProperty::GetDetails(Node, CPF_Edit, false); FString PrimaryCategory; if (UMaterialGraphNode* MatNode = Cast(Node)) { - WingPropHandle::Handles Handles = Props.GetDetails(Node, false, WingOut::Stdout); PrimaryCategory = MatNode->MaterialExpression->GetClass()->GetName(); - for (const TSharedPtr& H : Handles) + for (const FWingProperty& P : Props) { - FString Category = H->GetProperty()->GetMetaData(TEXT("Category")); + FString Category = P.GetCategory(); if ((Category == PrimaryCategory) == bPrimary) - WingPropHandle::Print(*H, Out); + P.Print(Out); } } else if (bPrimary) { - WingPropHandle::Handles Handles = Props.GetDetails(Node, false, WingOut::Stdout); - for (const TSharedPtr& H : Handles) - WingPropHandle::Print(*H, Out); + for (const FWingProperty& P : Props) + P.Print(Out); } } @@ -296,7 +295,7 @@ void WingGraphExport::EmitDetails() Details.Appendf(TEXT("\ndetails %s\n"), *WingUtils::FormatName(Node)); Details.Appendf(TEXT(" pos %d, %d\n"), Node->NodePosX, Node->NodePosY); - EmitNodeProperties(Node, Details, false); + EmitNodeProperties(Node, &Details, false); } } diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingPropHandle.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingPropHandle.cpp deleted file mode 100644 index 9bd6ccf0..00000000 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingPropHandle.cpp +++ /dev/null @@ -1,524 +0,0 @@ -#include "WingPropHandle.h" -#include "WingServer.h" -#include "WingActorComponent.h" -#include "WingUtils.h" -#include "WingTypes.h" -#include "Dom/JsonValue.h" -#include "PropertyEditorModule.h" -#include "IDetailTreeNode.h" -#include "Engine/Blueprint.h" -#include "Components/Widget.h" -#include "Components/PanelSlot.h" -#include "MaterialGraph/MaterialGraphNode.h" -#include "Materials/MaterialExpression.h" -#include "UObject/EnumProperty.h" -#include "EdGraph/EdGraphPin.h" - -///////////////////////////////////////////////////////////////////////////// -// -// Get Root -// -///////////////////////////////////////////////////////////////////////////// - -TSharedRef WingPropHandle::CreateGenerator() -{ - FPropertyEditorModule& Module = FModuleManager::GetModuleChecked("PropertyEditor"); - FPropertyRowGeneratorArgs Args; - Args.bShouldShowHiddenProperties = false; - return Module.CreatePropertyRowGenerator(Args); -} - -WingPropHandle::Root& WingPropHandle::GetRootForObject(UObject* Obj) -{ - for (Root& R : Roots) - { - if (R.Base == (uint8*)Obj) return R; - } - TSharedRef Gen = CreateGenerator(); - Gen->SetObjects({Obj}); - Root& R = Roots.AddDefaulted_GetRef(); - R.Struct = Obj->GetClass(); - R.Base = (uint8*)Obj; - R.End = R.Base + R.Struct->GetStructureSize(); - R.Generator = Gen; - return R; -} - -WingPropHandle::Root& WingPropHandle::GetRootForStruct(const UStruct* ScriptStruct, uint8* Data) -{ - for (Root& R : Roots) - { - if (R.Base == Data) return R; - } - TSharedRef Gen = CreateGenerator(); - TSharedPtr Wrapper = MakeShared(ScriptStruct, Data); - Gen->SetStructure(Wrapper); - Root& R = Roots.AddDefaulted_GetRef(); - R.Struct = ScriptStruct; - R.Base = Data; - R.End = Data + ScriptStruct->GetStructureSize(); - R.Generator = Gen; - return R; -} - -///////////////////////////////////////////////////////////////////////////// -// -// IsInsideRootObject -// -///////////////////////////////////////////////////////////////////////////// - -bool WingPropHandle::IsInsideRootObject(const Root& Root, IPropertyHandle& Handle) -{ - // Walk up to the topmost property handle that still has a property. - // Keep the shared pointers alive so the raw pointer stays valid. - TSharedPtr Held; - IPropertyHandle* Top = &Handle; - while (true) - { - TSharedPtr Parent = Top->GetParentHandle(); - if (!Parent.IsValid() || !Parent->GetProperty()) break; - Held = Parent; - Top = Held.Get(); - } - - // Get the address of the topmost property's data. - void* Addr = nullptr; - if (Top->GetValueData(Addr) != FPropertyAccess::Success || !Addr) - return false; - - uint8* DataPtr = (uint8*)Addr; - return DataPtr >= Root.Base && DataPtr < Root.End; -} - -///////////////////////////////////////////////////////////////////////////// -// -// All Tree Nodes -// -///////////////////////////////////////////////////////////////////////////// - -void WingPropHandle::AllTreeNodesRecursive(const TSharedRef& Node, FlatTree& Out) -{ - if (Node->GetNodeType() == EDetailNodeType::Category) - { - TArray> Children; - Node->GetChildren(Children); - for (const TSharedRef& Child : Children) - { - AllTreeNodesRecursive(Child, Out); - } - return; - } - - Out.Add(&*Node); -} - -WingPropHandle::FlatTree WingPropHandle::AllTreeNodes(Root& Root) -{ - FlatTree Result; - for (const TSharedRef& TreeRoot : Root.Generator->GetRootTreeNodes()) - { - AllTreeNodesRecursive(TreeRoot, Result); - } - return Result; -} - -///////////////////////////////////////////////////////////////////////////// -// -// AllProperties -// -///////////////////////////////////////////////////////////////////////////// - -WingPropHandle::Handles WingPropHandle::AllProperties(Root& Root, bool RootFilter) -{ - Handles Result; - for (IDetailTreeNode* Node : AllTreeNodes(Root)) - { - TSharedPtr Handle = Node->CreatePropertyHandle(); - if (Handle.IsValid() && Handle->GetProperty()) - { - if (RootFilter && !IsInsideRootObject(Root, *Handle)) - continue; - Result.Add(Handle); - } - } - return Result; -} - -WingPropHandle::Handles WingPropHandle::AllProperties(UObject* Obj, bool RootFilter) -{ - if (!Obj) return {}; - return AllProperties(GetRootForObject(Obj), RootFilter); -} - -WingPropHandle::Handles WingPropHandle::AllProperties(const UStruct* ScriptStruct, uint8* Data, bool RootFilter) -{ - if (!ScriptStruct || !Data) return {}; - return AllProperties(GetRootForStruct(ScriptStruct, Data), RootFilter); -} - -///////////////////////////////////////////////////////////////////////////// -// -// Named Property -// -///////////////////////////////////////////////////////////////////////////// - -TSharedPtr WingPropHandle::TryNamedProperty(Root& Root, FName Name, bool RootFilter) -{ - for (IDetailTreeNode* Node : AllTreeNodes(Root)) - { - if (Node->GetNodeName() != Name) continue; - TSharedPtr Handle = Node->CreatePropertyHandle(); - if (Handle.IsValid() && (!RootFilter || IsInsideRootObject(Root, *Handle))) - return Handle; - } - return nullptr; -} - -TSharedPtr WingPropHandle::TryNamedProperty(UObject* Obj, FName Name, bool RootFilter) -{ - if (!Obj) return nullptr; - return TryNamedProperty(GetRootForObject(Obj), Name, RootFilter); -} - -TSharedPtr WingPropHandle::TryNamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter) -{ - if (!ScriptStruct || !Data) return nullptr; - return TryNamedProperty(GetRootForStruct(ScriptStruct, Data), Name, RootFilter); -} - -TSharedPtr WingPropHandle::NamedProperty(UObject* Obj, FName Name, bool RootFilter, WingOut Errors) -{ - TSharedPtr Result = TryNamedProperty(Obj, Name, RootFilter); - if (!Result) - Errors.Printf(TEXT("ERROR: Property '%s' not found\n"), *Name.ToString()); - return Result; -} - -TSharedPtr WingPropHandle::NamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter, WingOut Errors) -{ - TSharedPtr Result = TryNamedProperty(ScriptStruct, Data, Name, RootFilter); - if (!Result) - Errors.Printf(TEXT("ERROR: Property '%s' not found\n"), *Name.ToString()); - return Result; -} - -///////////////////////////////////////////////////////////////////////////// -// -// GetDetails -// -///////////////////////////////////////////////////////////////////////////// - -WingPropHandle::Handles WingPropHandle::GetDetails(UObject* Obj, bool Mutable, WingOut Errors) -{ - bool RootFilter = false; - - if (!Obj) return {}; - - // Blueprints: redirect to the generated class CDO. - if (UBlueprint* BP = Cast(Obj)) - { - if (!BP->GeneratedClass) - { - Errors.Printf(TEXT("ERROR: Blueprint '%s' has no GeneratedClass\n"), *Obj->GetName()); - return {}; - } - Obj = BP->GeneratedClass->GetDefaultObject(); - } - - // UWingComponentReference is a class of our own creation, containing - // a pointer to a blueprint and a component name. Sometimes, the - // component is inherited. If so, and if you want to mutate it, you - // first have to create the override template in the child. If you're - // not mutating, you can just use the existing inherited template. - if (UWingComponentReference* Ref = Cast(Obj)) - { - Obj = Mutable ? Ref->GetMutableTemplate() : Ref->GetImmutableTemplate(); - if (!Obj) - { - Errors.Printf(TEXT("ERROR: Component '%s' has no template\n"), *Ref->VariableName.ToString()); - return {}; - } - } - - // Actors have components, which flood the property listing with hundreds - // of confusing additional properties. - if (Cast(Obj)) RootFilter = true; - - // Fetch the handles. - Handles Result = AllProperties(Obj, RootFilter); - - // Material graph nodes: also collect expression properties. - if (UMaterialGraphNode* MatNode = Cast(Obj)) - { - if (UMaterialExpression* Expr = MatNode->MaterialExpression) - { - Result.Append(AllProperties(Expr, true)); - } - } - - // Widgets: hide the Slot property, add slot properties. - // if (UWidget* Widget = Cast(Obj)) - // { - // Result.RemoveAll([](const TSharedPtr& H) - // { - // return H->GetProperty()->GetFName() == TEXT("Slot"); - // }); - // if (UPanelSlot* Slot = Widget->Slot) - // { - // Result.Append(AllProperties(Slot, false)); - // } - // } - - return Result; -} -///////////////////////////////////////////////////////////////////////////// -// -// Organize by Name -// -///////////////////////////////////////////////////////////////////////////// - -bool WingPropHandle::OrganizeByName(const Handles &HList, TMap> &Result, WingOut Errors) -{ - Result.Empty(); - TSet DuplicateNames; - for (const TSharedPtr &H : HList) - { - FName Name = H->GetProperty()->GetFName(); - if (Result.Contains(Name)) DuplicateNames.Add(Name); - else Result.Add(Name, H); - } - if (DuplicateNames.IsEmpty()) return true; - Errors.Print(TEXT("More than one property with name:")); - for (FName DupName : DuplicateNames) - { - Errors.Printf(TEXT(" %s"), *WingUtils::ExternalizeID(DupName)); - } - Errors.Print(TEXT("\n")); - return false; -} - -///////////////////////////////////////////////////////////////////////////// -// -// GetText / SetText -// -///////////////////////////////////////////////////////////////////////////// - -bool WingPropHandle::IsPinTypeProperty(FProperty* Prop) -{ - FStructProperty* StructProp = CastField(Prop); - return StructProp && StructProp->Struct == FEdGraphPinType::StaticStruct(); -} - -FString WingPropHandle::GetText(IPropertyHandle& Handle) -{ - // Pin types: use our human-readable format. - if (IsPinTypeProperty(Handle.GetProperty())) - { - void* Data = nullptr; - if (Handle.GetValueData(Data) != FPropertyAccess::Success || !Data) - UE_LOG(LogTemp, Fatal, TEXT("GetValueData failed for pin type property '%s'"), - *Handle.GetProperty()->GetName()); - return UWingTypes::TypeToText(*static_cast(Data)); - } - - // Class properties: use our human-readable type names. - if (CastField(Handle.GetProperty())) - { - UObject* ClassObj = nullptr; - if (Handle.GetValue(ClassObj) != FPropertyAccess::Success) - UE_LOG(LogTemp, Fatal, TEXT("GetValue failed for class property '%s'"), - *Handle.GetProperty()->GetName()); - if (!ClassObj) return TEXT("None"); - return UWingTypes::TypeToText(ClassObj); - } - - FString Result; - if (Handle.GetValueAsFormattedString(Result) != FPropertyAccess::Success) - UE_LOG(LogTemp, Fatal, TEXT("GetValueAsFormattedString failed for property '%s'"), - *Handle.GetProperty()->GetName()); - return Result; -} - -bool WingPropHandle::SetText(IPropertyHandle& Handle, const FString& Text, WingOut Errors) -{ - FProperty* Prop = Handle.GetProperty(); - - // Pin types: parse with our type parser. - if (IsPinTypeProperty(Prop)) - { - FEdGraphPinType PinType; - UWingTypes::Requirements Req; - Req.BlueprintType = true; - Req.Blueprintable = false; - Req.AllowContainer = true; - if (!UWingTypes::TextToType(Text, PinType, Req, Errors)) return false; - void* Data = nullptr; - if (Handle.GetValueData(Data) != FPropertyAccess::Success || !Data) - { - Errors.Printf(TEXT("ERROR: Cannot access data for property '%s'\n"), - *WingUtils::FormatName(Prop)); - return false; - } - Handle.NotifyPreChange(); - *static_cast(Data) = PinType; - Handle.NotifyPostChange(EPropertyChangeType::ValueSet); - return true; - } - - // Class properties: parse with our type parser. - if (FClassProperty* ClassProp = CastField(Prop)) - { - UObject* Class = nullptr; - if (!Text.IsEmpty()) - { - UWingTypes::Requirements Req; - Req.BlueprintType = true; - Req.Blueprintable = false; - Req.IsChildOf = ClassProp->MetaClass; - Class = UWingTypes::TextToOneObjectType(Text, Req, Errors); - if (!Class) return false; - } - if (Handle.SetValue(Class) != FPropertyAccess::Success) - { - Errors.Printf(TEXT("ERROR: Failed to set class property '%s'\n"), - *WingUtils::FormatName(Prop)); - return false; - } - return true; - } - - // Enums: canonicalize the string with our smarter prefix matching. - FString Value = Text; - UEnum* Enum = nullptr; - if (FByteProperty* ByteProp = CastField(Prop)) - Enum = ByteProp->Enum; - if (FEnumProperty* EnumProp = CastField(Prop)) - Enum = EnumProp->GetEnum(); - if (Enum != nullptr) - { - int64 EnumValue; - if (!WingUtils::StringToEnum(Enum, Value, EnumValue, Errors)) return false; - Value = Enum->GetNameStringByValue(EnumValue); - } - - FPropertyAccess::Result Result = Handle.SetValueFromFormattedString(Value); - if (Result != FPropertyAccess::Success) - { - Errors.Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"), - *Value, *WingUtils::FormatName(Prop), *Prop->GetCPPType()); - return false; - } - return true; -} - -///////////////////////////////////////////////////////////////////////////// -// -// SetJson -// -///////////////////////////////////////////////////////////////////////////// - -bool WingPropHandle::SetJson(IPropertyHandle& Handle, const TSharedPtr& JsonValue, WingOut Errors) -{ - FPropertyAccess::Result Result; - - switch (JsonValue->Type) - { - case EJson::String: - return SetText(Handle, JsonValue->AsString(), Errors); - - case EJson::Boolean: - Result = Handle.SetValue(JsonValue->AsBool()); - break; - - case EJson::Number: - Result = Handle.SetValue(JsonValue->AsNumber()); - break; - - default: - Result = FPropertyAccess::Fail; - break; - } - - if (Result != FPropertyAccess::Success) - { - Errors.Printf(TEXT("ERROR: Failed to set property '%s'\n"), - *WingUtils::FormatName(Handle.GetProperty())); - return false; - } - return true; -} - -bool WingPropHandle::PopulateFromJson(TArray>& Props, const FJsonObject& Json, bool AllOptional, WingOut Errors) -{ - bool Ok = true; - - // Organize the properties by name. - TMap> OrganizedProps; - if (!OrganizeByName(Props, OrganizedProps, Errors)) Ok = false; - - // Parse the keys in the json, make sure they're syntactically valid and - // that they match the names of actual properties, and that there are no dups. - TSet Specified; - for (const auto& KV : Json.Values) - { - FName Name = WingUtils::CheckInternalizeID(KV.Key, Errors); - if (Name.IsNone()) { Ok = false; continue; } - if (!OrganizedProps.Contains(Name)) - { - Errors.Printf(TEXT("ERROR: Unknown parameter '%s'\n"), *KV.Key); - Ok = false; - } - if (!WingUtils::FindNoDuplicateName(Specified, Name, TEXT("parameter"), Errors)) Ok = false; - } - - // Make sure that all required properties have been specified. - if (!AllOptional) - { - for (const TSharedPtr &H : Props) - { - if (H->HasMetaData(TEXT("Optional"))) continue; - FName Name = H->GetProperty()->GetFName(); - if (!Specified.Contains(Name)) - { - Errors.Printf(TEXT("Required parameter %s not specified\n"), - *WingUtils::ExternalizeID(Name)); - Ok = false; - } - } - } - - // If anything is wrong, return early without setting anything. - if (!Ok) return false; - - // Populate each property from JSON. This could fail too, but at this - // point, we're committed. - for (const auto& KV : Json.Values) - { - FName Name = WingUtils::CheckInternalizeID(KV.Key, Errors); - if (!SetJson(*OrganizedProps[Name], KV.Value, Errors)) Ok = false; - } - return Ok; -} - - -///////////////////////////////////////////////////////////////////////////// -// -// Print -// -///////////////////////////////////////////////////////////////////////////// - -void WingPropHandle::Print(IPropertyHandle& Handle, FStringBuilderBase& Out) -{ - FString Value = GetText(Handle); - Value.ReplaceInline(TEXT("\r"), TEXT(" ")); - Value.ReplaceInline(TEXT("\n"), TEXT(" ")); - if (Value.Len() > 100) Value = Value.Left(100) + TEXT("..."); - - bool bEditable = !Handle.IsEditConst(); - - Out.Appendf(TEXT(" %s %s %s = %s\n"), - bEditable ? TEXT("editable") : TEXT("readonly"), - *UWingTypes::TypeToText(Handle.GetProperty()), - *WingUtils::FormatName(Handle), - *Value); -} diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp index 491505f3..d6d61515 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp @@ -338,6 +338,16 @@ FString FWingProperty::GetTruncatedText(int32 MaxLen) const } +void FWingProperty::Print(WingOut Out) const +{ + bool bEditable = !Prop->HasAnyPropertyFlags(CPF_EditConst); + Out.Printf(TEXT(" %s %s %s = %s\n"), + bEditable ? TEXT("editable") : TEXT("readonly"), + *UWingTypes::TypeToText(Prop), + *WingUtils::FormatName(Prop), + *GetTruncatedText(100)); +} + FString FWingProperty::GetCategory() const { FString Result = Prop->GetMetaData(TEXT("Category")); diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp index dc94f68e..881d2140 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp @@ -146,6 +146,7 @@ void UWingServer::Initialize(FSubsystemCollectionBase& Collection) } BuildWingHandlerRegistry(); + ModulesChangedHandle = FModuleManager::Get().OnModulesChanged().AddUObject(this, &UWingServer::OnModulesChanged); LogCapture.bEnabled = false; LogCapture.Install(); bRunning = true; @@ -154,6 +155,8 @@ void UWingServer::Initialize(FSubsystemCollectionBase& Collection) void UWingServer::Deinitialize() { + FModuleManager::Get().OnModulesChanged().Remove(ModulesChangedHandle); + if (!bRunning) { Super::Deinitialize(); @@ -472,6 +475,7 @@ void UWingServer::AddHandler(UObject* Obj, const FString& Name, UObject* Config, void UWingServer::BuildWingHandlerRegistry() { + WingHandlerRegistry.Empty(); for (UClass* Class : WingUtils::CollectHandlerClasses()) { UWingHandler* CDO = Cast(Class->GetDefaultObject()); @@ -480,6 +484,14 @@ void UWingServer::BuildWingHandlerRegistry() WingHandlerRegistry.Sort([](const FWingHandlerConfig& A, const FWingHandlerConfig& B) { return A.Name < B.Name; }); } +void UWingServer::OnModulesChanged(FName ModuleName, EModuleChangeReason Reason) +{ + if (Reason == EModuleChangeReason::ModuleLoaded) + { + BuildWingHandlerRegistry(); + } +} + FWingHandlerConfig* UWingServer::FindHandler(const FString& Name) { int32 Index = Algo::LowerBoundBy(WingHandlerRegistry, Name, [](const FWingHandlerConfig& H) { return H.Name; }); diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingFactories.h b/Plugins/UEWingman/Source/UEWingman/Public/WingFactories.h index b0cef705..a89f9ea9 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingFactories.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingFactories.h @@ -20,12 +20,20 @@ public: // pop up dialog boxes. In those cases, we deal with it // primarily by adding those factories to the blacklist, // and then implementing replacement factories. - static bool IsBlacklisted(UClass* FactoryClass); + static bool IsBlacklisted(TSubclassOf FactoryClass); + + // Check if the factory class can be used to create assets. + // makes sure it's not abstract, calls CanCreateNew, + // calls ShouldShowInNewMenu, and verifies not blacklisted. + static bool CanCreate(TSubclassOf FactoryClass); + + // Get the names of the editable properties for a factory class. + static TArray GetParameterNames(TSubclassOf FactoryClass); // This takes a factory name and turns it into a string // that we can present to the user. Mainly, it removes // the word 'Factory', and anything that comes after. - static FString DeriveFactoryName(UClass* FactoryClass); + static FString DeriveFactoryName(TSubclassOf FactoryClass); // Verifies that the asset path is a valid path, and also // that there's not something already there at that path. diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingGraphExport.h b/Plugins/UEWingman/Source/UEWingman/Public/WingGraphExport.h index 2c89345c..a3592759 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingGraphExport.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingGraphExport.h @@ -1,7 +1,7 @@ #pragma once #include "CoreMinimal.h" -#include "WingPropHandle.h" +#include "WingProperty.h" #include "Engine/Blueprint.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" @@ -62,7 +62,7 @@ private: void Traverse(UEdGraphNode* Node); void SortNodes(); void EmitNode(UEdGraphNode* Node); - void EmitNodeProperties(UEdGraphNode* Node, FStringBuilderBase& Out, bool bPrimary); + void EmitNodeProperties(UEdGraphNode* Node, WingOut Out, bool bPrimary); void EmitLocalVariables(); void EmitGraph(); void EmitDetails(); @@ -76,7 +76,6 @@ private: UEdGraph* Graph; - WingPropHandle Props; // Data populated by passes. TArray SortedNodes; diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingPropHandle.h b/Plugins/UEWingman/Source/UEWingman/Public/WingPropHandle.h deleted file mode 100644 index a343d45f..00000000 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingPropHandle.h +++ /dev/null @@ -1,103 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "IPropertyRowGenerator.h" -#include "PropertyHandle.h" -#include "WingHandler.h" - -class FJsonObject; -class FJsonValue; - -// WingPropHandle: A module that provides easy access to -// IPropertyHandle objects. To use this module, construct a -// WingPropHandle, then use it to fetch properties from -// objects. The property handles returned are valid until -// the WingPropHandle is destroyed. -// -// In UE Wingman, we are encouraging the use of -// IPropertyHandle over FProperty. The FProperty API sucks: -// it's buggy and unreliable. Use IPropertyHandle instead. -// - -class WingPropHandle -{ -public: - using Handles = TArray>; - using FlatTree = TArray>; - - WingPropHandle() {} - - // Get all properties of a UObject. Returns the handles. - // If RootFilter is true, only properties inside the root object are returned. - Handles AllProperties(UObject* Obj, bool RootFilter); - - // Get all properties of a struct. Does not copy — the data - // pointer must remain valid for the lifetime of this object. - // If RootFilter is true, only properties inside the root object are returned. - Handles AllProperties(const UStruct* ScriptStruct, uint8* Data, bool RootFilter); - - // Get a single named property from a UObject. - // If RootFilter is true, only properties inside the root object are returned. - TSharedPtr TryNamedProperty(UObject* Obj, FName Name, bool RootFilter); - - // Get a single named property from a struct. - // If RootFilter is true, only properties inside the root object are returned. - TSharedPtr TryNamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter); - - // Like TryNamedProperty, but prints an error if the property is not found. - TSharedPtr NamedProperty(UObject* Obj, FName Name, bool RootFilter, WingOut Errors); - TSharedPtr NamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter, WingOut Errors); - - // Get "details panel" properties for an object. Handles special - // cases: blueprints (redirects to CDO), component references - // (redirects to template), material graph nodes (includes - // expression properties), widgets (includes slot properties). - Handles GetDetails(UObject* Obj, bool Mutable, WingOut Errors); - - // Organize properties by name. If there's more than one property - // of a given name, print an error and returns false, and returns a map - // that contains one of the two duplicates. - static bool OrganizeByName(const Handles &H, TMap> &Result, WingOut Errors); - - // Get/set text. Compared to the built in methods for getting and setting - // text, these support more concise enum values, FEdGraphPinType, and - // more concise Class properties. - static FString GetText(IPropertyHandle& Handle); - static bool SetText(IPropertyHandle& Handle, const FString& Text, WingOut Errors); - - // Store a Json value into a property handle. The Json value must be - // a string, number, or boolean. - static bool SetJson(IPropertyHandle& Handle, const TSharedPtr& JsonValue, WingOut Errors); - - // Populate a whole bunch of properties from a Json object. If - // AllOptional is true, the Json may supply a subset of the properties. - // If not, the Json must supply all of them, excepting properties that - // are explicitly marked Optional. - static bool PopulateFromJson(TArray>& Props, const FJsonObject& Json, bool AllOptional, WingOut Errors); - - // Print a single property in a standardized format: - // editable|readonly Type Name = Value - static void Print(IPropertyHandle& Handle, FStringBuilderBase& Out); - -private: - struct Root - { - const UStruct* Struct = nullptr; - uint8* Base = nullptr; - uint8* End = nullptr; - TSharedPtr Generator; - }; - - static bool IsPinTypeProperty(FProperty* Prop); - static bool IsInsideRootObject(const Root& Root, IPropertyHandle& Handle); - TArray Roots; - - static TSharedRef CreateGenerator(); - Root& GetRootForObject(UObject* Obj); - Root& GetRootForStruct(const UStruct* ScriptStruct, uint8* Data); - static void AllTreeNodesRecursive(const TSharedRef& Node, FlatTree& Out); - static FlatTree AllTreeNodes(Root& Root); - - Handles AllProperties(Root& Root, bool RootFilter); - static TSharedPtr TryNamedProperty(Root& Root, FName Name, bool RootFilter); -}; diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h b/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h index bdf03695..de11e10a 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h @@ -57,6 +57,10 @@ struct FWingProperty // FString GetTruncatedText(int32 MaxLen) const; + // Print the property's type, name, and value. + // + void Print(WingOut Out) const; + // Get the Category metadata. // FString GetCategory() const; diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingServer.h b/Plugins/UEWingman/Source/UEWingman/Public/WingServer.h index 58638cf3..0ea57cdd 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingServer.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingServer.h @@ -74,6 +74,8 @@ private: FLogCaptureOutputDevice LogCapture; // installed once at startup, enabled per-request TArray WingHandlerRegistry; // sorted by Name void BuildWingHandlerRegistry(); + void OnModulesChanged(FName ModuleName, EModuleChangeReason Reason); + FDelegateHandle ModulesChangedHandle; FWingHandlerConfig* FindHandler(const FString& Name); // Handle a complete JSON line and return the response JSON