Lots of refactoring

This commit is contained in:
2026-04-04 05:22:08 -04:00
parent ba63a40641
commit 92e41c857a
15 changed files with 105 additions and 691 deletions

View File

@@ -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<IPropertyHandle> 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<FWingProperty> 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);

View File

@@ -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<UClass*> FactoryClasses;
GetDerivedClasses(UFactory::StaticClass(), FactoryClasses);
for (UClass* Class : FactoryClasses)
{
if (Class->HasAnyClassFlags(CLASS_Abstract)) continue;
UFactory* CDO = Class->GetDefaultObject<UFactory>();
if (!CDO->CanCreateNew() || !CDO->ShouldShowInNewMenu()) continue;
WingPropHandle Props;
TArray<TSharedPtr<IPropertyHandle>> 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);

View File

@@ -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);
}
}
};

View File

@@ -28,6 +28,6 @@ public:
}
virtual void Handle() override
{
WingManual::Commands(EWingHandlerKind::Normal, Query, Verbose);
WingManual::Commands(EWingHandlerKind::Create, Query, Verbose);
}
};

View File

@@ -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<UClass*> SupportedFactories;
for (const FWingHandlerConfig& H : UWingServer::AllHandlers())
{
if (H.Kind != EWingHandlerKind::Create) continue;
UClass* FactoryClass = Cast<UClass>(H.Config.Get());
if (FactoryClass && FactoryClass->IsChildOf(UFactory::StaticClass()))
SupportedFactories.Add(FactoryClass);
}
TArray<UClass*> FactoryClasses;
GetDerivedClasses(UFactory::StaticClass(), FactoryClasses);
TArray<FString> Results;
TArray<FString> Supported;
TArray<FString> Unsupported;
for (UClass *Factory : FactoryClasses)
{
if (Factory->HasAnyClassFlags(CLASS_Abstract)) continue;
UFactory *CDO = Factory->GetDefaultObject<UFactory>();
if (!CDO->CanCreateNew()) continue;
if (!CDO->ShouldShowInNewMenu()) continue;
TArray<FName> Params = FWingProperty::GetNames(Factory, CPF_Edit);
if (!WingFactories::CanCreate(Factory)) continue;
TArray<FName> 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);
}
}
};

View File

@@ -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<UFactory> 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<UFactory> FactoryClass)
{
if (FactoryClass == nullptr) return false;
if (FactoryClass->HasAnyClassFlags(CLASS_Abstract)) return false;
if (IsBlacklisted(FactoryClass)) return false;
UFactory* CDO = FactoryClass->GetDefaultObject<UFactory>();
return CDO->CanCreateNew() && CDO->ShouldShowInNewMenu();
}
TArray<FName> WingFactories::GetParameterNames(TSubclassOf<UFactory> 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<UFactory> FactoryClass)
{
FString Name = FactoryClass->GetName();
int32 Index = Name.Find(TEXT("Factory"));

View File

@@ -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<FWingProperty> Props = FWingProperty::GetDetails(Node, CPF_Edit, false);
FString PrimaryCategory;
if (UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Node))
{
WingPropHandle::Handles Handles = Props.GetDetails(Node, false, WingOut::Stdout);
PrimaryCategory = MatNode->MaterialExpression->GetClass()->GetName();
for (const TSharedPtr<IPropertyHandle>& 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<IPropertyHandle>& 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);
}
}

View File

@@ -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<IPropertyRowGenerator> WingPropHandle::CreateGenerator()
{
FPropertyEditorModule& Module = FModuleManager::GetModuleChecked<FPropertyEditorModule>("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<IPropertyRowGenerator> 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<IPropertyRowGenerator> Gen = CreateGenerator();
TSharedPtr<FStructOnScope> Wrapper = MakeShared<FStructOnScope>(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<IPropertyHandle> Held;
IPropertyHandle* Top = &Handle;
while (true)
{
TSharedPtr<IPropertyHandle> 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<IDetailTreeNode>& Node, FlatTree& Out)
{
if (Node->GetNodeType() == EDetailNodeType::Category)
{
TArray<TSharedRef<IDetailTreeNode>> Children;
Node->GetChildren(Children);
for (const TSharedRef<IDetailTreeNode>& Child : Children)
{
AllTreeNodesRecursive(Child, Out);
}
return;
}
Out.Add(&*Node);
}
WingPropHandle::FlatTree WingPropHandle::AllTreeNodes(Root& Root)
{
FlatTree Result;
for (const TSharedRef<IDetailTreeNode>& 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<IPropertyHandle> 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<IPropertyHandle> WingPropHandle::TryNamedProperty(Root& Root, FName Name, bool RootFilter)
{
for (IDetailTreeNode* Node : AllTreeNodes(Root))
{
if (Node->GetNodeName() != Name) continue;
TSharedPtr<IPropertyHandle> Handle = Node->CreatePropertyHandle();
if (Handle.IsValid() && (!RootFilter || IsInsideRootObject(Root, *Handle)))
return Handle;
}
return nullptr;
}
TSharedPtr<IPropertyHandle> WingPropHandle::TryNamedProperty(UObject* Obj, FName Name, bool RootFilter)
{
if (!Obj) return nullptr;
return TryNamedProperty(GetRootForObject(Obj), Name, RootFilter);
}
TSharedPtr<IPropertyHandle> WingPropHandle::TryNamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter)
{
if (!ScriptStruct || !Data) return nullptr;
return TryNamedProperty(GetRootForStruct(ScriptStruct, Data), Name, RootFilter);
}
TSharedPtr<IPropertyHandle> WingPropHandle::NamedProperty(UObject* Obj, FName Name, bool RootFilter, WingOut Errors)
{
TSharedPtr<IPropertyHandle> Result = TryNamedProperty(Obj, Name, RootFilter);
if (!Result)
Errors.Printf(TEXT("ERROR: Property '%s' not found\n"), *Name.ToString());
return Result;
}
TSharedPtr<IPropertyHandle> WingPropHandle::NamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter, WingOut Errors)
{
TSharedPtr<IPropertyHandle> 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<UBlueprint>(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<UWingComponentReference>(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<AActor>(Obj)) RootFilter = true;
// Fetch the handles.
Handles Result = AllProperties(Obj, RootFilter);
// Material graph nodes: also collect expression properties.
if (UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Obj))
{
if (UMaterialExpression* Expr = MatNode->MaterialExpression)
{
Result.Append(AllProperties(Expr, true));
}
}
// Widgets: hide the Slot property, add slot properties.
// if (UWidget* Widget = Cast<UWidget>(Obj))
// {
// Result.RemoveAll([](const TSharedPtr<IPropertyHandle>& 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<FName, TSharedPtr<IPropertyHandle>> &Result, WingOut Errors)
{
Result.Empty();
TSet<FName> DuplicateNames;
for (const TSharedPtr<IPropertyHandle> &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<FStructProperty>(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<FEdGraphPinType*>(Data));
}
// Class properties: use our human-readable type names.
if (CastField<FClassProperty>(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<FEdGraphPinType*>(Data) = PinType;
Handle.NotifyPostChange(EPropertyChangeType::ValueSet);
return true;
}
// Class properties: parse with our type parser.
if (FClassProperty* ClassProp = CastField<FClassProperty>(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<FByteProperty>(Prop))
Enum = ByteProp->Enum;
if (FEnumProperty* EnumProp = CastField<FEnumProperty>(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<FJsonValue>& 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<TSharedPtr<IPropertyHandle>>& Props, const FJsonObject& Json, bool AllOptional, WingOut Errors)
{
bool Ok = true;
// Organize the properties by name.
TMap<FName, TSharedPtr<IPropertyHandle>> 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<FName> 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<IPropertyHandle> &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);
}

View File

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

View File

@@ -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<UWingHandler>(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; });

View File

@@ -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<UFactory> 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<UFactory> FactoryClass);
// Get the names of the editable properties for a factory class.
static TArray<FName> GetParameterNames(TSubclassOf<UFactory> 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<UFactory> FactoryClass);
// Verifies that the asset path is a valid path, and also
// that there's not something already there at that path.

View File

@@ -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<UEdGraphNode*> SortedNodes;

View File

@@ -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<TSharedPtr<IPropertyHandle>>;
using FlatTree = TArray<IDetailTreeNode *, TInlineAllocator<500>>;
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<IPropertyHandle> 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<IPropertyHandle> TryNamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter);
// Like TryNamedProperty, but prints an error if the property is not found.
TSharedPtr<IPropertyHandle> NamedProperty(UObject* Obj, FName Name, bool RootFilter, WingOut Errors);
TSharedPtr<IPropertyHandle> 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<FName, TSharedPtr<IPropertyHandle>> &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<FJsonValue>& 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<TSharedPtr<IPropertyHandle>>& 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<IPropertyRowGenerator> Generator;
};
static bool IsPinTypeProperty(FProperty* Prop);
static bool IsInsideRootObject(const Root& Root, IPropertyHandle& Handle);
TArray<Root> Roots;
static TSharedRef<IPropertyRowGenerator> CreateGenerator();
Root& GetRootForObject(UObject* Obj);
Root& GetRootForStruct(const UStruct* ScriptStruct, uint8* Data);
static void AllTreeNodesRecursive(const TSharedRef<IDetailTreeNode>& Node, FlatTree& Out);
static FlatTree AllTreeNodes(Root& Root);
Handles AllProperties(Root& Root, bool RootFilter);
static TSharedPtr<IPropertyHandle> TryNamedProperty(Root& Root, FName Name, bool RootFilter);
};

View File

@@ -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;

View File

@@ -74,6 +74,8 @@ private:
FLogCaptureOutputDevice LogCapture; // installed once at startup, enabled per-request
TArray<FWingHandlerConfig> 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