Compare commits

...

10 Commits

Author SHA1 Message Date
8951065670 Give FindOneWithInternalID the power to handle arrays of smart pointers.
Eliminate FindNoneWithInternalID, finish migration to FindNoDuplicateNames.
2026-04-06 04:37:28 -04:00
7aac8f194a Precompute broad type categories in WingTypes.
Move the broad-category classification logic out of TypeName_Search and
into WingTypes, store the result in the type registry, and update
TypeName_Search to print the cached category.
2026-04-06 02:56:21 -04:00
5206700067 Harden UE Wingman request handling and numeric property conversion.
Switch the Wingman protocol to null-delimited JSON, rework the server's
socket buffering and send logic, and document the bugs found during the
review. Also refactor WingProperty's numeric setters into clearer helper
paths while preserving the existing conversion rules.
2026-04-06 01:44:21 -04:00
9c1f474170 work on material parameters 2026-04-05 03:20:51 -04:00
c949a4db05 Better handling of property mutability, and also, walk into structs. 2026-04-04 23:57:59 -04:00
bd138e2790 Fix bug in package loading 2026-04-04 21:07:13 -04:00
fb5b774bfe better expression of class properties 2026-04-04 20:10:22 -04:00
47acf1aca4 Simplification of handlers 2026-04-04 18:12:10 -04:00
c006531bd4 Create_OneArg now working 2026-04-04 17:59:00 -04:00
92e41c857a Lots of refactoring 2026-04-04 05:22:08 -04:00
68 changed files with 1412 additions and 1477 deletions

1
.gitignore vendored
View File

@@ -51,3 +51,4 @@ GPF-output/**
__pycache__/
.clangd-query/
COMMIT.txt

Binary file not shown.

View File

@@ -0,0 +1,100 @@
# Unsupported Factories with 1 Class-Type Parameter
## Standard candidates (class picker, flag-based filtering)
All of these follow the same basic pattern: TSubclassOf<Base>, open a
class picker dialog, filter on class flags.
- AudioSynesthesiaNRTFactory — AudioSynesthesiaNRTClass
TSubclassOf<UAudioSynesthesiaNRT>
Flags: no abstract, no deprecated, no newer-version-exists
- AudioSynesthesiaNRTSettingsFactory — AudioSynesthesiaNRTSettingsClass
TSubclassOf<UAudioSynesthesiaNRTSettings>
Flags: no abstract, no deprecated, no newer-version-exists
- AudioSynesthesiaSettingsFactory — AudioSynesthesiaSettingsClass
TSubclassOf<UAudioSynesthesiaSettings>
Flags: no abstract, no deprecated, no newer-version-exists
- CurveFactory — CurveClass
TSubclassOf<UCurveBase>
Flags: no abstract, no deprecated, no newer-version-exists
Also checks CanCreateAssetOfClass()
- DataAssetFactory — DataAssetClass
TSubclassOf<UDataAsset>
Flags: no abstract, no deprecated, no newer-version-exists, no hide-dropdown
Also checks CanCreateAssetOfClass()
- InputAction_Factory — InputActionClass
TSubclassOf<UInputAction>
Flags: no abstract, no deprecated, no newer-version-exists, no hide-dropdown
Skips dialog if no subtypes exist
- InputMappingContext_Factory — InputMappingContextClass
TSubclassOf<UInputMappingContext>
Flags: no abstract, no deprecated, no newer-version-exists, no hide-dropdown
Skips dialog if no subtypes exist
- PhysicalMaterialFactoryNew — PhysicalMaterialClass
TSubclassOf<UPhysicalMaterial>
Flags: no abstract, no deprecated, no newer-version-exists
Also checks CanCreateAssetOfClass()
- PhysicalMaterialMaskFactory — PhysicalMaterialMaskClass
TSubclassOf<UPhysicalMaterialMask>
Flags: no abstract, no deprecated, no newer-version-exists
Also checks CanCreateAssetOfClass()
- SoundSourceEffectFactory — SoundEffectSourcepresetClass
TSubclassOf<USoundEffectSourcePreset>
Flags: no abstract, no deprecated, no newer-version-exists
- SoundSubmixEffectFactory — SoundEffectSubmixPresetClass
TSubclassOf<USoundEffectSubmixPreset>
Flags: no abstract, no deprecated, no newer-version-exists
## Slightly unusual candidates
- CameraAssetFactory — CameraDirectorClass
TSubclassOf<UCameraDirector>
Flags: no abstract, no deprecated (does NOT check newer-version-exists)
Allows "None" option (parameter can be null)
- SlateWidgetStyleAssetFactory — StyleType
TSubclassOf<USlateWidgetStyleContainerBase>
Flags: no abstract, no deprecated, no newer-version-exists
Explicitly excludes the base class itself
- TemplateSequenceFactoryNew — BoundActorClass
TSubclassOf<UObject> but dialog restricts to actors only (bIsActorsOnly)
Effective base class is AActor
No explicit flag filtering beyond class viewer defaults
## Complex / special cases (probably not suitable for Create_ClassArg)
- ControlRigBlueprintFactory — ParentClass
TSubclassOf<UControlRig>
Complex custom filter: native-only, must have IsBlueprintBase="true"
metadata, no blueprint parents. Also calls CanCreateBlueprintOfClass
in FactoryCreateNew. Dialog gated behind a CVar.
- LiveLinkBlueprintVirtualSubjectFactory — ParentClass
ParentClass is hardcoded. The actual configured parameter is "Role"
(TSubclassOf<ULiveLinkRole>) with a custom modal dialog and a filter
that checks IsAllowableBlueprintVariableType on the role's structs.
Different parameter than what SysInfo_Factories reported.
## Not class parameters at all (remove from consideration)
- MetaSoundFactory — ReferencedMetaSoundObject
TObjectPtr<UObject>, not TSubclassOf. Optional reference to an
existing MetaSound object for preset initialization.
- MetaSoundSourceFactory — ReferencedMetaSoundObject
Same as above.
- ObjectMixerBlueprintFilterFactory — ParentClass
Hardcoded to UObjectMixerBlueprintObjectFilter in constructor.
No config dialog. Nothing to configure.

View File

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

@@ -26,7 +26,7 @@ public:
UPROPERTY(EditAnywhere, meta=(Description="Asset to delete"))
FString Asset;
UPROPERTY(meta=(Optional, Description="If true, skip reference check and force delete"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="If true, skip reference check and force delete"))
bool Force = false;
virtual void Register() override

View File

@@ -21,13 +21,13 @@ class UWing_Asset_Search : public UWingHandler
GENERATED_BODY()
public:
UPROPERTY(meta=(Optional, Description="Substring to match against asset package paths"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Substring to match against asset package paths"))
FString Query;
UPROPERTY(meta=(Optional, Description="Asset class name to filter by, e.g. Blueprint, Material, StaticMesh"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Asset class name to filter by, e.g. Blueprint, Material, StaticMesh"))
FString Type;
UPROPERTY(meta=(Optional, Description="Maximum number of results (default 50)"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Maximum number of results (default 50)"))
int32 Limit = 50;
virtual void Register() override
@@ -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

@@ -32,10 +32,10 @@ public:
UPROPERTY(EditAnywhere, meta=(Description="Type of graph: function or macro"))
FString GraphType;
UPROPERTY(meta=(Optional, Description="Input variables, one per line"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Input variables, one per line"))
FString InputVariables;
UPROPERTY(meta=(Optional, Description="Output variables, one per line"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Output variables, one per line"))
FString OutputVariables;
virtual void Register() override
@@ -75,8 +75,9 @@ public:
// Check graph name uniqueness and legality
FName InternalID = WingUtils::CheckProposedName(Graph, WingOut::Stdout);
if (InternalID.IsNone()) return;
if (!WingUtils::FindNoneWithInternalID(InternalID, WingUtils::AllGraphs(BP), TEXT("Graph"), WingOut::Stdout))
return;
TSet<FName> Names;
if (!WingUtils::FindNoDuplicateName(Names, InternalID, TEXT("Graph"), WingOut::Stdout)) return;
if (!WingUtils::FindNoDuplicateNames(Names, WingUtils::AllGraphs(BP), TEXT("Graph"), WingOut::Stdout)) return;
// Parse and validate variables before making changes
WingVariables Vars;

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

@@ -27,7 +27,7 @@ public:
UPROPERTY(EditAnywhere, meta=(Description="Interface name to remove"))
FString Interface;
UPROPERTY(meta=(Optional, Description="If true, keep the function graphs as regular functions"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="If true, keep the function graphs as regular functions"))
bool PreserveFunctions = false;
virtual void Register() override
@@ -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

@@ -1,101 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "Factories/Factory.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFactories.h"
#include "WingPropHandle.h"
#include "WingTypes.h"
#include "Factories/BlueprintFactory.h"
#include "Factories/BlueprintMacroFactory.h"
#include "Factories/AnimBlueprintFactory.h"
#include "WidgetBlueprintFactory.h"
#include "EditorUtilityBlueprintFactory.h"
#include "EditorUtilityWidgetBlueprintFactory.h"
#include "Create_Blueprint.generated.h"
UCLASS()
class UWing_Create_Blueprint : public UWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, meta=(Description="Full asset path for the new asset (e.g. '/Game/MyFolder/MyAsset')"))
FString Path;
UPROPERTY(meta=(Optional, Description="The parent class for the new blueprint"))
FString ParentClass;
static UClass *BlueprintInterfaceFactoryStaticClass()
{
return FindObject<UClass>(nullptr, TEXT("/Script/UnrealEd.BlueprintInterfaceFactory"));
}
virtual void Register() override
{
UWingServer::AddHandler(this, TEXT("Create_Blueprint"),
UBlueprintFactory::StaticClass(), EWingHandlerKind::Create,
TEXT("Create a normal blueprint"));
UWingServer::AddHandler(this, TEXT("Create_BlueprintMacroLibrary"),
UBlueprintMacroFactory::StaticClass(), EWingHandlerKind::Create,
TEXT("Create a blueprint macro library"));
UWingServer::AddHandler(this, TEXT("Create_WidgetBlueprint"),
UWidgetBlueprintFactory::StaticClass(), EWingHandlerKind::Create,
TEXT("Create a widget blueprint"));
UWingServer::AddHandler(this, TEXT("Create_EditorUtilityBlueprint"),
UEditorUtilityBlueprintFactory::StaticClass(), EWingHandlerKind::Create,
TEXT("Create an editor utility blueprint"));
UWingServer::AddHandler(this, TEXT("Create_EditorUtilityWidgetBlueprint"),
UEditorUtilityWidgetBlueprintFactory::StaticClass(), EWingHandlerKind::Create,
TEXT("Create an editor utility widget blueprint"));
if (BlueprintInterfaceFactoryStaticClass() != nullptr)
UWingServer::AddHandler(this, TEXT("Create_BlueprintInterface"),
BlueprintInterfaceFactoryStaticClass(), EWingHandlerKind::Create,
TEXT("Create a blueprint interface"));
}
virtual void Handle() override
{
if (!WingFactories::CheckNewAssetPath(Path, WingOut::Stdout)) return;
// Resolve parent class, if specified.
UClass *ParentClassObj = nullptr;
if (!ParentClass.IsEmpty())
{
UWingTypes::Requirements Req;
Req.BlueprintType = false;
Req.Blueprintable = true;
Req.AllowContainer = false;
ParentClassObj = UWingTypes::TextToOneObjectType(ParentClass, Req, WingOut::Stdout);
if (!ParentClassObj) return;
}
// Make the factory.
UClass *FactoryClass = Cast<UClass>(ConfigurationObject);
UFactory *Factory = NewObject<UFactory>(GetTransientPackage(), FactoryClass);
if (Factory == nullptr)
{
WingOut::Stdout.Printf(TEXT("ERROR: factory creation failed (bug)\n"));
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;
}
// Create the asset using the factory.
UObject *Blueprint = WingFactories::CreateAsset(Path, Factory, WingOut::Stdout);
if (Blueprint == nullptr) return;
WingOut::Stdout.Printf(TEXT("Created.\n"));
}
};

View File

@@ -0,0 +1,303 @@
#pragma once
#include "CoreMinimal.h"
#include "Factories/Factory.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingTypes.h"
#include "WingProperty.h"
#include "WingFactories.h"
#include "Create_ClassArg.generated.h"
struct FWingClassArgConfig
{
const TCHAR *Factory;
const TCHAR *Base;
bool ValidPointer = true; // eg, not nullptr
bool ConcreteClass = false; // eg, not abstract class
bool LatestVersion = true; // eg, not deprecated and no newer version.
bool ShowInDropDown = true; // eg, not hide drop down
bool NativeClass = false; // eg, not generated class
bool TrueSubclass = false; // eg, not the base class itself
bool CanCreateAsset = false; // calls 'CanCreateAssetOfClass'
bool BlueprintBase = false; //
bool Blueprintable = false; //
bool CanCreateBlueprint = false; //
};
UCLASS()
class UWingClassArg : public UObject
{
GENERATED_BODY()
public:
FWingClassArgConfig Config;
UPROPERTY()
TSubclassOf<UFactory> FactoryClass;
UPROPERTY()
TObjectPtr<UClass> BaseClass;
};
UCLASS()
class UWing_Create_ClassArg : public UWingHandler
{
GENERATED_BODY()
UPROPERTY(editAnywhere)
FString Path;
UPROPERTY(EditAnywhere)
FString Class;
public:
void RegisterConfigs(TArrayView<const FWingClassArgConfig> Configs)
{
// Build a map of factory name -> UClass for quick lookup.
TMap<FString, UClass*> FactoryMap;
TArray<UClass*> FactoryClasses;
GetDerivedClasses(UFactory::StaticClass(), FactoryClasses);
for (UClass* C : FactoryClasses) FactoryMap.Add(C->GetName(), C);
for (const FWingClassArgConfig& Cfg : Configs)
{
// Look up the factory class.
UClass** FoundFactory = FactoryMap.Find(Cfg.Factory);
if (!FoundFactory)
{
UE_LOG(LogTemp, Warning, TEXT("Create_ClassArg: factory '%s' not found, skipping"), Cfg.Factory);
continue;
}
// Look up the base class.
UClass* BaseClass = FindObject<UClass>(nullptr, Cfg.Base);
if (!BaseClass)
{
UE_LOG(LogTemp, Warning, TEXT("Create_ClassArg: base class '%s' not found, skipping"), Cfg.Base);
continue;
}
// Create the config object.
UWingClassArg* Arg = NewObject<UWingClassArg>(this);
Arg->Config = Cfg;
Arg->FactoryClass = *FoundFactory;
Arg->BaseClass = BaseClass;
// Register the command.
FString FactoryName = WingFactories::DeriveFactoryName(*FoundFactory);
FString CommandName = FString::Printf(TEXT("Create_%s"), *FactoryName);
FString Doc = FString::Printf(TEXT("Create a new %s asset."), *FactoryName);
UWingServer::AddHandler(this, CommandName, Arg, EWingHandlerKind::Create, *FoundFactory, Doc);
}
}
virtual void Register() override
{
RegisterConfigs({
// --- Standard candidates ---
{ .Factory = TEXT("AudioSynesthesiaNRTFactory"),
.Base = TEXT("/Script/AudioSynesthesia.AudioSynesthesiaNRT"),
.ConcreteClass = true },
{ .Factory = TEXT("AudioSynesthesiaNRTSettingsFactory"),
.Base = TEXT("/Script/AudioSynesthesia.AudioSynesthesiaNRTSettings"),
.ConcreteClass = true },
{ .Factory = TEXT("AudioSynesthesiaSettingsFactory"),
.Base = TEXT("/Script/AudioSynesthesia.AudioSynesthesiaSettings"),
.ConcreteClass = true },
{ .Factory = TEXT("CurveFactory"),
.Base = TEXT("/Script/Engine.CurveBase"),
.ConcreteClass = true, .CanCreateAsset = true },
{ .Factory = TEXT("DataAssetFactory"),
.Base = TEXT("/Script/Engine.DataAsset"),
.ConcreteClass = true, .CanCreateAsset = true },
{ .Factory = TEXT("InputAction_Factory"),
.Base = TEXT("/Script/EnhancedInput.InputAction"),
.ConcreteClass = true },
{ .Factory = TEXT("InputMappingContext_Factory"),
.Base = TEXT("/Script/EnhancedInput.InputMappingContext"),
.ConcreteClass = true },
{ .Factory = TEXT("PhysicalMaterialFactoryNew"),
.Base = TEXT("/Script/PhysicsCore.PhysicalMaterial"),
.ConcreteClass = true, .CanCreateAsset = true },
{ .Factory = TEXT("PhysicalMaterialMaskFactory"),
.Base = TEXT("/Script/Engine.PhysicalMaterialMask"),
.ConcreteClass = true, .CanCreateAsset = true },
{ .Factory = TEXT("SoundSourceEffectFactory"),
.Base = TEXT("/Script/Engine.SoundEffectSourcePreset"),
.ConcreteClass = true },
{ .Factory = TEXT("SoundSubmixEffectFactory"),
.Base = TEXT("/Script/Engine.SoundEffectSubmixPreset"),
.ConcreteClass = true },
// --- Slightly unusual ---
{ .Factory = TEXT("CameraAssetFactory"),
.Base = TEXT("/Script/GameplayCameras.CameraDirector"),
.ValidPointer = false, .ConcreteClass = true },
{ .Factory = TEXT("SlateWidgetStyleAssetFactory"),
.Base = TEXT("/Script/SlateCore.SlateWidgetStyleContainerBase"),
.ConcreteClass = true, .TrueSubclass = true },
{ .Factory = TEXT("TemplateSequenceFactoryNew"),
.Base = TEXT("/Script/Engine.Actor") },
// --- Blueprint factories ---
{ .Factory = TEXT("BlueprintFactory"),
.Base = TEXT("/Script/CoreUObject.Object"),
.Blueprintable = true, .CanCreateBlueprint = true },
{ .Factory = TEXT("BlueprintMacroFactory"),
.Base = TEXT("/Script/CoreUObject.Object"),
.Blueprintable = true, .CanCreateBlueprint = true },
// BlueprintFunctionLibraryFactory is handled by Create_NoArgs.
// BlueprintInterfaceFactory is handled by Create_NoArgs.
{ .Factory = TEXT("WidgetBlueprintFactory"),
.Base = TEXT("/Script/CoreUObject.Object"),
.Blueprintable = true, .CanCreateBlueprint = true },
{ .Factory = TEXT("EditorUtilityBlueprintFactory"),
.Base = TEXT("/Script/CoreUObject.Object"),
.Blueprintable = true, .CanCreateBlueprint = true },
{ .Factory = TEXT("EditorUtilityWidgetBlueprintFactory"),
.Base = TEXT("/Script/CoreUObject.Object"),
.Blueprintable = true, .CanCreateBlueprint = true },
{ .Factory = TEXT("InterchangeBlueprintPipelineBaseFactory"),
.Base = TEXT("/Script/InterchangeCore.InterchangePipelineBase"),
.Blueprintable = true, .CanCreateBlueprint = true },
{ .Factory = TEXT("InterchangeEditorBlueprintPipelineBaseFactory"),
.Base = TEXT("/Script/InterchangeEditorPipelines.InterchangeEditorPipelineBase"),
.Blueprintable = true, .CanCreateBlueprint = true },
{ .Factory = TEXT("ControlRigBlueprintFactory"),
.Base = TEXT("/Script/ControlRig.ControlRig"),
.NativeClass = true, .BlueprintBase = true, .Blueprintable = true, .CanCreateBlueprint = true },
});
}
virtual void Handle() override
{
// Get the configuration.
UWingClassArg *ConfObj = Cast<UWingClassArg>(Configuration->Config.Get());
if (ConfObj == nullptr)
{
WingOut::Stdout.Print(TEXT("ERROR: Create_ClassArg config object not set properly (bug)\n"));
return;
}
FWingClassArgConfig &Config = ConfObj->Config;
// Verify that the specified path is good.
if (!WingFactories::CheckNewAssetPath(Path, WingOut::Stdout)) return;
// Parse the class string.
UClass *ClassObj = nullptr;
if (!Class.IsEmpty())
{
UWingTypes::Requirements Req;
Req.Blueprintable = Config.Blueprintable;
Req.AllowContainer = false;
Req.AllowNone = !Config.ValidPointer;
Req.IsChildOf = ConfObj->BaseClass;
FEdGraphPinType PinType;
if (!UWingTypes::TextToType(Class, PinType, Req, WingOut::Stdout)) return;
ClassObj = Cast<UClass>(PinType.PinSubCategoryObject.Get());
}
// Check constraints.
if (Config.ValidPointer && (ClassObj == nullptr))
{
WingOut::Stdout.Print(TEXT("ERROR: factory requires a non-empty class to be specified."));
return;
}
if (ClassObj != nullptr)
{
if (Config.ConcreteClass && ClassObj->HasAnyClassFlags(CLASS_Abstract))
{
WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is abstract.\n"), *ClassObj->GetName());
return;
}
if (Config.LatestVersion && ClassObj->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists))
{
WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is deprecated or has a newer version.\n"), *ClassObj->GetName());
return;
}
if (Config.ShowInDropDown && ClassObj->HasAnyClassFlags(CLASS_HideDropDown))
{
WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is hidden.\n"), *ClassObj->GetName());
return;
}
if (Config.NativeClass && !ClassObj->HasAnyClassFlags(CLASS_Native))
{
WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is not a native class.\n"), *ClassObj->GetName());
return;
}
if (Config.TrueSubclass && (ClassObj == ConfObj->BaseClass))
{
WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is the base class itself, a subclass is required.\n"), *ClassObj->GetName());
return;
}
if (Config.CanCreateAsset && !ClassObj->CanCreateAssetOfClass())
{
WingOut::Stdout.Printf(TEXT("ERROR: cannot create an asset of class '%s'.\n"), *ClassObj->GetName());
return;
}
if (Config.BlueprintBase && !ClassObj->GetBoolMetaDataHierarchical(FBlueprintMetadata::MD_IsBlueprintBase))
{
WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is not marked as a blueprint base.\n"), *ClassObj->GetName());
return;
}
if (Config.CanCreateBlueprint && !FKismetEditorUtilities::CanCreateBlueprintOfClass(ClassObj))
{
WingOut::Stdout.Printf(TEXT("ERROR: cannot create a blueprint of class '%s'.\n"), *ClassObj->GetName());
return;
}
}
// Make the factory.
UFactory *Factory = NewObject<UFactory>(GetTransientPackage(), ConfObj->FactoryClass);
if (Factory == nullptr)
{
WingOut::Stdout.Printf(TEXT("ERROR: factory creation failed (bug)\n"));
return;
}
// Set the 'Class' property.
TArray<FWingProperty> Props = FWingProperty::GetVisible(Factory);
FWingProperty::Remove(Props, TEXT("BlueprintType"));
if (Props.Num() != 1)
{
WingOut::Stdout.Printf(TEXT("ERROR: Factory unexpectedly has multiple config properties (bug)\n"));
return;
}
if (!CastField<FClassProperty>(Props[0].Prop))
{
WingOut::Stdout.Printf(TEXT("ERROR: Factory was expected to have a 'class' property (bug)\n"));
return;
}
if (!Props[0].SetObject(ClassObj, WingOut::Stdout)) return;
// Create the asset using the factory.
UObject *Blueprint = WingFactories::CreateAsset(Path, Factory, WingOut::Stdout);
if (Blueprint == nullptr) return;
WingOut::Stdout.Printf(TEXT("Created.\n"));
}
};

View File

@@ -5,12 +5,11 @@
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFactories.h"
#include "WingPropHandle.h"
#include "Factories/BlueprintFunctionLibraryFactory.h"
#include "Create_UsingFactory.generated.h"
#include "Create_NoArgs.generated.h"
UCLASS()
class UWing_Create_UsingFactory : public UWingHandler
class UWing_Create_NoArgs : public UWingHandler
{
GENERATED_BODY()
@@ -22,32 +21,33 @@ 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);
FString Doc = FString::Printf(TEXT("Create a new %s asset."), *FactoryName);
UWingServer::AddHandler(this, CommandName, Class, EWingHandlerKind::Create, Doc);
UWingServer::AddHandler(this, CommandName, nullptr, EWingHandlerKind::Create, Class, Doc);
}
UWingServer::AddHandler(this, TEXT("Create_BlueprintFunctionLibrary"),
UBlueprintFunctionLibraryFactory::StaticClass(), EWingHandlerKind::Create,
nullptr, EWingHandlerKind::Create,
UBlueprintFunctionLibraryFactory::StaticClass(),
TEXT("Create a blueprint function library"));
UClass *BlueprintInterfaceFactory = FindObject<UClass>(nullptr, TEXT("/Script/UnrealEd.BlueprintInterfaceFactory"));
if (BlueprintInterfaceFactory)
UWingServer::AddHandler(this, TEXT("Create_BlueprintInterface"),
nullptr, EWingHandlerKind::Create,
BlueprintInterfaceFactory,
TEXT("Create a blueprint interface"));
}
virtual void Handle() override
{
UClass* FactoryClass = Cast<UClass>(ConfigurationObject);
UClass* FactoryClass = Configuration->FactoryClass.Get();
UFactory* Factory = NewObject<UFactory>(GetTransientPackage(), FactoryClass);
WingFactories::CreateAsset(Path, Factory, WingOut::Stdout);
WingOut::Stdout.Printf(TEXT("Created.\n"));

View File

@@ -6,7 +6,6 @@
#include "WingFetcher.h"
#include "WingProperty.h"
#include "WingUtils.h"
#include "WingTypes.h"
#include "Details_Dump.generated.h"
UCLASS()
@@ -30,7 +29,7 @@ public:
UObject* Target = F.Walk(Object).Cast<UObject>();
if (!Target) return;
TArray<FWingProperty> Props = FWingProperty::GetDetails(Target, CPF_Edit, false);
TArray<FWingProperty> Props = FWingProperty::GetDetails(Target, false);
// Group by category, preserving within-category order.
TSortedMap<FString, TArray<FWingProperty>> Categories;
@@ -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

@@ -32,7 +32,7 @@ public:
UObject* Obj = F.Walk(Object).Cast<UObject>();
if (!Obj) return;
TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, CPF_Edit, false);
TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, false);
FWingProperty* P = WingUtils::FindOneWithExternalID(Property, Props, TEXT("Property"), WingOut::Stdout);
if (!P) return;

View File

@@ -35,10 +35,10 @@ public:
UObject* Obj = F.Walk(Object).Cast<UObject>();
if (!Obj) return;
TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, CPF_Edit, true);
TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, true);
FWingProperty* P = WingUtils::FindOneWithExternalID(Property, Props, TEXT("Property"), WingOut::Stdout);
if (!P) return;
if (P->SetText(Value, WingOut::Stdout))
WingOut::Stdout.Print(TEXT("OK\n"));
}

View File

@@ -38,7 +38,7 @@ public:
return;
}
TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, CPF_Edit, true);
TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, true);
// Validation pass — resolve all properties before modifying anything.
for (const auto& Pair : Properties.Json->Values)

View File

@@ -15,10 +15,10 @@ class UWing_Documentation_Commands : public UWingHandler
GENERATED_BODY()
public:
UPROPERTY(meta=(Optional, Description="Substring filter for command names"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Substring filter for command names"))
FString Query;
UPROPERTY(meta=(Optional, Description="If true, return full details including parameter types and descriptions"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="If true, return full details including parameter types and descriptions"))
bool Verbose = false;
virtual void Register() override

View File

@@ -15,10 +15,10 @@ class UWing_Documentation_CreateAssets : public UWingHandler
GENERATED_BODY()
public:
UPROPERTY(meta=(Optional, Description="Substring filter for command names"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Substring filter for command names"))
FString Query;
UPROPERTY(meta=(Optional, Description="If true, return full details including parameter types and descriptions"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="If true, return full details including parameter types and descriptions"))
bool Verbose = false;
virtual void Register() override
@@ -28,6 +28,6 @@ public:
}
virtual void Handle() override
{
WingManual::Commands(EWingHandlerKind::Normal, Query, Verbose);
WingManual::Commands(EWingHandlerKind::Create, Query, Verbose);
}
};

View File

@@ -39,10 +39,9 @@ public:
FBPVariableDescription* Var = WingUtils::FindOneWithExternalID(Dispatcher, BP->NewVariables, TEXT("Dispatcher"), WingOut::Stdout);
if (!Var) return;
TObjectPtr<UEdGraph>* SigGraph = WingUtils::FindOneWithExternalID(Dispatcher, BP->DelegateSignatureGraphs, TEXT("Dispatcher Signature Graph"), WingOut::Stdout);
if (!SigGraph) return;
TObjectPtr<UEdGraph> Graph = WingUtils::FindOneWithExternalID(Dispatcher, BP->DelegateSignatureGraphs, TEXT("Dispatcher Signature Graph"), WingOut::Stdout);
if (!Graph) return;
UEdGraph* Graph = *SigGraph;
FName VarFName = Var->VarName;
// Remove the member variable (also destroys referencing nodes)

View File

@@ -66,7 +66,7 @@ public:
// Parse the json array, turning it into an array of spawn node entries.
TArray<FSpawnNodeEntry> Entries;
FSpawnNodeEntry Entry;
TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry, CPF_None);
TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry);
for (const TSharedPtr<FJsonValue>& Elt : Nodes.Array)
{
if (!FWingProperty::PopulateFromJson(Props, *Elt, false, WingOut::Stdout)) return;

View File

@@ -24,7 +24,7 @@ public:
UPROPERTY(EditAnywhere, meta=(Description="Query string, can contain *"))
FString Query;
UPROPERTY(meta=(Optional, Description="Maximum number of results (default 50)"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Maximum number of results (default 50)"))
int32 MaxResults = 50;
UPROPERTY(EditAnywhere, meta=(Description="Target graph"))

View File

@@ -91,7 +91,7 @@ public:
UEdGraphNode* Node = F.Node(Entry.Node).Cast<UEdGraphNode>();
if (!Node) return;
TArray<FWingProperty> All = FWingProperty::GetDetails(Node, CPF_Edit, true);
TArray<FWingProperty> All = FWingProperty::GetDetails(Node, true);
FWingProperty *P = WingUtils::FindOneWithExternalID(Entry.Name, All, TEXT("Property"), WingOut::Stdout);
if (!P) return;
@@ -121,7 +121,7 @@ public:
}
FSetNodeDefaultEntry Entry;
TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry, CPF_None);
TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry);
for (const TSharedPtr<FJsonValue>& PinVal : Pins.Array)
{
if (!FWingProperty::PopulateFromJson(Props, *PinVal, false, WingOut::Stdout)) continue;

View File

@@ -57,7 +57,7 @@ public:
int32 SuccessCount = 0;
FMoveNodeEntry Entry;
TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry, CPF_None);
TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry);
for (const TSharedPtr<FJsonValue>& Elt : Nodes.Array)
{
if (!FWingProperty::PopulateFromJson(Props, *Elt, false, WingOut::Stdout)) continue;

View File

@@ -57,7 +57,7 @@ public:
int32 TotalCount = Connections.Array.Num();
FConnectPinsEntry Entry;
TArray<FWingProperty> EntryProps = FWingProperty::GetAll(&Entry, CPF_None);
TArray<FWingProperty> EntryProps = FWingProperty::GetAll(&Entry);
for (const TSharedPtr<FJsonValue>& ConnVal : Connections.Array)
{
if (!FWingProperty::PopulateFromJson(EntryProps, *ConnVal, false, WingOut::Stdout))

View File

@@ -24,7 +24,7 @@ struct FDisconnectPinEntry
UPROPERTY()
FString Pin;
UPROPERTY(meta=(Optional))
UPROPERTY(EditAnywhere, meta=(Optional))
FString TargetPin;
};
@@ -57,7 +57,7 @@ public:
int32 TotalDisconnected = 0;
FDisconnectPinEntry Entry;
TArray<FWingProperty> EntryProps = FWingProperty::GetAll(&Entry, CPF_None);
TArray<FWingProperty> EntryProps = FWingProperty::GetAll(&Entry);
for (const TSharedPtr<FJsonValue>& DiscVal : Disconnections.Array)
{
if (!FWingProperty::PopulateFromJson(EntryProps, *DiscVal, false, WingOut::Stdout)) continue;

View File

@@ -26,7 +26,7 @@ public:
UPROPERTY(EditAnywhere, meta=(Description="Path to graph"))
FString Graph;
UPROPERTY(meta=(Optional, Description="True to include less-significant details"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="True to include less-significant details"))
bool IncludeDetails;
virtual void Register() override

View File

@@ -24,56 +24,27 @@ public:
UPROPERTY(EditAnywhere, meta=(Description="Target material instance"))
FString MaterialInstance;
UPROPERTY(EditAnywhere, meta=(Description="Parameter name to clear"))
UPROPERTY(EditAnywhere, meta=(Description="Parameter ID: 'Name', 'Layer:N:Name', or 'Blend:N:Name'"))
FString Parameter;
UPROPERTY(EditAnywhere, meta=(Description="Parameter association: 'Global', 'Layer', or 'Blend'. Default: 'Global'", Optional))
FString ParameterAssociation = TEXT("Global");
UPROPERTY(EditAnywhere, meta=(Description="Layer/blend index (0-based). Only used when ParameterAssociation is 'Layer' or 'Blend'", Optional))
int32 ParameterLayer = INDEX_NONE;
virtual void Register() override
{
UWingServer::AddHandler(this,
TEXT("Remove a parameter override from a Material Instance, reverting it to the parent material's value."));
}
virtual void Handle() override
{
WingFetcher F(WingOut::Stdout);
UMaterialInstanceConstant* MI = F.Asset(MaterialInstance).Cast<UMaterialInstanceConstant>();
if (!MI) return;
// Parse the association string.
EMaterialParameterAssociation Association;
if (!WingMaterialParameter::ParseMaterialParameterAssociation(ParameterAssociation, Association, WingOut::Stdout))
// Parse the parameter ID.
FMaterialParameterInfo ID;
if (!WingMaterialParameter::ParseID(Parameter, ID, WingOut::Stdout))
return;
FMaterialParameterInfo ParamID(*Parameter, Association, ParameterLayer);
// Remove the override from all parameter arrays.
auto RemoveFrom = [&](auto& Arr) {
return Arr.RemoveAll([&](auto& Entry) { return Entry.ParameterInfo == ParamID; });
};
int32 Removed = 0;
Removed += RemoveFrom(MI->ScalarParameterValues);
Removed += RemoveFrom(MI->VectorParameterValues);
Removed += RemoveFrom(MI->DoubleVectorParameterValues);
Removed += RemoveFrom(MI->TextureParameterValues);
Removed += RemoveFrom(MI->TextureCollectionParameterValues);
Removed += RemoveFrom(MI->RuntimeVirtualTextureParameterValues);
Removed += RemoveFrom(MI->SparseVolumeTextureParameterValues);
Removed += RemoveFrom(MI->FontParameterValues);
if (Removed == 0)
{
WingOut::Stdout.Printf(TEXT("No override found for parameter '%s' (association=%s layer=%d) on %s"),
*Parameter, *ParameterAssociation, ParameterLayer, *WingUtils::FormatName(MI));
return;
}
WingOut::Stdout.Printf(TEXT("Cleared override for '%s' on %s\n"),
*Parameter, *WingUtils::FormatName(MI));
if (!WingMaterialParameter::RemoveOverride(ID, MI, WingOut::Stdout)) return
WingOut::Stdout.Printf(TEXT("Removed override\n"));
}
};

View File

@@ -36,23 +36,7 @@ public:
if (!MI) return;
auto AllParams = WingMaterialParameter::GetMaterialParameters(MI);
// Overridden parameters first.
bool bHasOverrides = false;
for (auto& [Info, Meta] : AllParams)
{
if (!Meta.bOverride) continue;
if (!bHasOverrides) { WingOut::Stdout.Print(TEXT("\nOverridden Parameters:\n")); bHasOverrides = true; }
WingMaterialParameter::FormatMaterialParameter(Info, Meta);
}
// Inherited (non-overridden) parameters.
bool bHasInherited = false;
for (auto& [Info, Meta] : AllParams)
{
if (Meta.bOverride) continue;
if (!bHasInherited) { WingOut::Stdout.Print(TEXT("\nInherited Parameters (not overridden):\n")); bHasInherited = true; }
WingMaterialParameter::FormatMaterialParameter(Info, Meta);
}
WingMaterialParameter::PrintAll(AllParams, true);
if (AllParams.IsEmpty()) WingOut::Stdout.Printf(TEXT("No material parameters.\n"));
}
};

View File

@@ -25,15 +25,9 @@ public:
UPROPERTY(EditAnywhere, meta=(Description="Target material instance"))
FString MaterialInstance;
UPROPERTY(EditAnywhere, meta=(Description="Parameter name to set"))
UPROPERTY(EditAnywhere, meta=(Description="Parameter ID: 'Name', 'Layer:N:Name', or 'Blend:N:Name'"))
FString Parameter;
UPROPERTY(EditAnywhere, meta=(Description="Parameter association: 'Global', 'Layer', or 'Blend'. Default: 'Global'", Optional))
FString ParameterAssociation = TEXT("Global");
UPROPERTY(EditAnywhere, meta=(Description="Layer/blend index (0-based). Only used when ParameterAssociation is 'Layer' or 'Blend'", Optional))
int32 ParameterLayer = INDEX_NONE;
UPROPERTY(EditAnywhere, meta=(Description="Value to set (uses Unreal text format, e.g. '0.5' for scalar, '(R=1,G=0,B=0,A=1)' for vector)"))
FString Value;
@@ -48,30 +42,19 @@ public:
UMaterialInstanceConstant* MI = F.Asset(MaterialInstance).Cast<UMaterialInstanceConstant>();
if (!MI) return;
// Parse the association string.
EMaterialParameterAssociation Association;
if (!WingMaterialParameter::ParseMaterialParameterAssociation(ParameterAssociation, Association, WingOut::Stdout))
// Parse the parameter ID.
FMaterialParameterInfo ID;
if (!WingMaterialParameter::ParseID(Parameter, ID, WingOut::Stdout))
return;
// Build the parameter ID to look up.
FMaterialParameterInfo ParamID(*Parameter, Association, ParameterLayer);
// Find it in the material's parameter map.
auto AllParams = WingMaterialParameter::GetMaterialParameters(MI);
FMaterialParameterMetadata* Found = AllParams.Find(ParamID);
if (!Found)
{
WingOut::Stdout.Printf(TEXT("No parameter named '%s' with association=%s layer=%d"),
*Parameter, *ParameterAssociation, ParameterLayer);
return;
}
if (Found->PrimitiveDataIndex != INDEX_NONE)
{
WingOut::Stdout.Printf(TEXT("Parameter '%s' uses custom primitive data and cannot be set on a material instance"), *Parameter);
return;
}
FMaterialParameterMetadata* Meta =
WingMaterialParameter::FindParameter(AllParams, ID, WingOut::Stdout);
if (!Meta) return;
if (!WingMaterialParameter::CheckNoCustomPrimitiveData(ID, *Meta, WingOut::Stdout)) return;
EMaterialParameterType Type = Found->Value.Type;
EMaterialParameterType Type = Meta->Value.Type;
switch (Type)
{
@@ -83,7 +66,7 @@ public:
WingOut::Stdout.Printf(TEXT("Failed to parse '%s' as a float"), *Value);
return;
}
MI->SetScalarParameterValueEditorOnly(ParamID, ScalarValue);
MI->SetScalarParameterValueEditorOnly(ID, ScalarValue);
break;
}
case EMaterialParameterType::Vector:
@@ -94,14 +77,13 @@ public:
WingOut::Stdout.Printf(TEXT("Failed to parse '%s' as a color/vector (expected format: '(R=1,G=0,B=0,A=1)')"), *Value);
return;
}
MI->SetVectorParameterValueEditorOnly(ParamID, Color);
MI->SetVectorParameterValueEditorOnly(ID, Color);
break;
}
default:
WingOut::Stdout.Printf(TEXT("Parameters of type %d (see EMaterialParameterType) are not implemented"), (int)Type);
return;
}
WingOut::Stdout.Printf(TEXT("Set '%s' = %s on %s\n"),
*Parameter, *Value, *WingUtils::FormatName(MI));
WingOut::Stdout.Printf(TEXT("Assigned.\n"));
}
};

View File

@@ -35,11 +35,7 @@ public:
if (!Mat) return;
auto AllParams = WingMaterialParameter::GetMaterialParameters(Mat);
for (auto& [Info, Meta] : AllParams)
{
WingMaterialParameter::FormatMaterialParameter(Info, Meta);
}
WingMaterialParameter::PrintAll(AllParams, false);
if (AllParams.IsEmpty()) WingOut::Stdout.Printf(TEXT("No material parameters.\n"));
}
};

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,44 @@ 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;
if (H.FactoryClass.IsValid())
SupportedFactories.Add(H.FactoryClass.Get());
}
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

@@ -1,32 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingTokenizer.h"
#include "Test_Sanitizer.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Test_Sanitizer : public UWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, meta=(Description="The string to sanitize"))
FString Input;
virtual void Register() override
{
UWingServer::AddHandler(this,
TEXT("Test the sanitizer by sanitizing a string and printing the result."));
}
virtual void Handle() override
{
WingOut::Stdout.Printf(TEXT("%s\n"), *WingTokenizer::ExternalizeID(FName(Input)));
}
};

View File

@@ -1,33 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingTokenizer.h"
#include "Test_Tokenizer.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Test_Tokenizer : public UWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, meta=(Description="The string to tokenize"))
FString Input;
virtual void Register() override
{
UWingServer::AddHandler(this,
TEXT("Test the tokenizer by tokenizing a string and printing the result."));
}
virtual void Handle() override
{
WingTokenizer T(Input);
T.PrintEverything(WingOut::StdoutBuffer);
}
};

View File

@@ -1,63 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingTypes.h"
#include "Test_TypeToText.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Test_TypeToText : public UWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, meta=(Description="The type name to parse, e.g. 'Array<Vector>'"))
FString Input;
virtual void Register() override
{
UWingServer::AddHandler(this,
TEXT("Test the type parser by parsing a type name and dumping the resulting FEdGraphPinType."));
}
virtual void Handle() override
{
FEdGraphPinType PinType;
UWingTypes::Requirements Require;
Require.BlueprintType = false;
Require.Blueprintable = false;
Require.AllowContainer = true;
bool OK = UWingTypes::TextToType(Input, PinType, Require, WingOut::Stdout);
auto& Out = WingOut::Stdout;
Out.Printf(TEXT("ParseResult: %s\n"), OK ? TEXT("OK") : TEXT("FAILED"));
Out.Printf(TEXT("PinCategory: %s\n"), *PinType.PinCategory.ToString());
Out.Printf(TEXT("PinSubCategory: %s\n"), *PinType.PinSubCategory.ToString());
Out.Printf(TEXT("PinSubCategoryObject: %s\n"),
PinType.PinSubCategoryObject.IsValid()
? *PinType.PinSubCategoryObject->GetPathName()
: TEXT("(none)"));
Out.Printf(TEXT("ContainerType: %d\n"), (int32)PinType.ContainerType);
if (PinType.IsMap())
{
Out.Printf(TEXT("ValueTerminalCategory: %s\n"), *PinType.PinValueType.TerminalCategory.ToString());
Out.Printf(TEXT("ValueTerminalSubCategory: %s\n"), *PinType.PinValueType.TerminalSubCategory.ToString());
Out.Printf(TEXT("ValueTerminalSubCategoryObject: %s\n"),
PinType.PinValueType.TerminalSubCategoryObject.IsValid()
? *PinType.PinValueType.TerminalSubCategoryObject->GetPathName()
: TEXT("(none)"));
}
if (OK)
{
FString RoundTrip = UWingTypes::TypeToText(PinType);
Out.Printf(TEXT("TypeToText: %s\n"), RoundTrip.IsEmpty() ? TEXT("(empty)") : *RoundTrip);
}
}
};

View File

@@ -1,41 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingTokenizer.h"
#include "Test_Unsanitize.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Test_Unsanitize : public UWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, meta=(Description="The sanitized identifier to unsanitize"))
FString Input;
virtual void Register() override
{
UWingServer::AddHandler(this,
TEXT("Test the unsanitizer by unsanitizing a string and printing the result."));
}
virtual void Handle() override
{
FString Error;
FName Result = WingTokenizer::TryInternalizeID(Input, Error);
if (!Error.IsEmpty())
{
WingOut::Stdout.Printf(TEXT("Error: %s\n"), *Error);
}
if (!Result.IsNone())
{
WingOut::Stdout.Printf(TEXT("Result: %s\n"), *Result.ToString());
}
}
};

View File

@@ -26,10 +26,10 @@ public:
UPROPERTY(EditAnywhere, meta=(Description="Substring filter for type names"))
FString Query;
UPROPERTY(meta=(Optional, Description="Maximum number of results"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Maximum number of results"))
int32 Limit = 100;
UPROPERTY(meta=(Optional, Description="If true, include all types, not just BlueprintType/Blueprintable ones"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="If true, include all types, not just BlueprintType/Blueprintable ones"))
bool Exhaustive = false;
virtual void Register() override
@@ -43,23 +43,6 @@ public:
return Info.IsUserDefined && Info.PinSubCategoryObject.StartsWith(TEXT("/Game/"));
}
static FString BroadCategory(const UWingTypes::Info& Info)
{
if (Info.PinSubCategoryObject.IsEmpty()) return TEXT("Primitive");
if (Info.PinCategory == UEdGraphSchema_K2::PC_Enum) return TEXT("Enum");
if (Info.PinCategory == UEdGraphSchema_K2::PC_Struct) return TEXT("Struct");
if (Info.PinCategory == UEdGraphSchema_K2::PC_Interface) return TEXT("Interface");
if (Info.NativeParent)
{
if (Info.NativeParent->IsChildOf(UUserWidget::StaticClass())) return TEXT("Widget");
if (Info.NativeParent->IsChildOf(UActorComponent::StaticClass())) return TEXT("ActorComponent");
if (Info.NativeParent->IsChildOf(APawn::StaticClass())) return TEXT("Pawn");
if (Info.NativeParent->IsChildOf(AActor::StaticClass())) return TEXT("Actor");
if (Info.NativeParent->IsChildOf(UDataAsset::StaticClass())) return TEXT("DataAsset");
}
return TEXT("Object");
}
static int Importance(const UWingTypes::Info& Info)
{
if (IsProjectDefined(Info)) return 1;
@@ -94,9 +77,9 @@ public:
{
const UWingTypes::Info& Info = *Matches[i];
if (IsProjectDefined(Info))
WingOut::Stdout.Printf(TEXT("%s (%s, User-Defined)\n"), *Info.Short, *BroadCategory(Info));
WingOut::Stdout.Printf(TEXT("%s (%s, User-Defined)\n"), *Info.Short, *Info.BroadCategory.ToString());
else
WingOut::Stdout.Printf(TEXT("%s (%s)\n"), *Info.Short, *BroadCategory(Info));
WingOut::Stdout.Printf(TEXT("%s (%s)\n"), *Info.Short, *Info.BroadCategory.ToString());
}
if (Count >= Limit)
{

View File

@@ -21,16 +21,16 @@ public:
UPROPERTY(EditAnywhere, meta=(Description="Path to a blueprint, graph, or custom event node"))
FString Object;
UPROPERTY(meta=(Optional, Description="Blueprint variables, one per line"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Blueprint variables, one per line"))
FString BlueprintVariables;
UPROPERTY(meta=(Optional, Description="Input variables, one per line"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Input variables, one per line"))
FString InputVariables;
UPROPERTY(meta=(Optional, Description="Output variables, one per line"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Output variables, one per line"))
FString OutputVariables;
UPROPERTY(meta=(Optional, Description="Local variables, one per line"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Local variables, one per line"))
FString LocalVariables;
virtual void Register() override

View File

@@ -21,16 +21,16 @@ public:
UPROPERTY(EditAnywhere, meta=(Description="Path to a blueprint, graph, or custom event node"))
FString Object;
UPROPERTY(meta=(Optional, Description="Blueprint variables, one per line"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Blueprint variables, one per line"))
FString BlueprintVariables;
UPROPERTY(meta=(Optional, Description="Input variables, one per line"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Input variables, one per line"))
FString InputVariables;
UPROPERTY(meta=(Optional, Description="Output variables, one per line"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Output variables, one per line"))
FString OutputVariables;
UPROPERTY(meta=(Optional, Description="Local variables, one per line"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Local variables, one per line"))
FString LocalVariables;
virtual void Register() override

View File

@@ -21,16 +21,16 @@ public:
UPROPERTY(EditAnywhere, meta=(Description="Path to a blueprint, graph, or custom event node"))
FString Object;
UPROPERTY(meta=(Optional, Description="Blueprint variable names to remove, comma-separated"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Blueprint variable names to remove, comma-separated"))
FString BlueprintVariables;
UPROPERTY(meta=(Optional, Description="Input variable names to remove, comma-separated"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Input variable names to remove, comma-separated"))
FString InputVariables;
UPROPERTY(meta=(Optional, Description="Output variable names to remove, comma-separated"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Output variable names to remove, comma-separated"))
FString OutputVariables;
UPROPERTY(meta=(Optional, Description="Local variable names to remove, comma-separated"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Local variable names to remove, comma-separated"))
FString LocalVariables;
virtual void Register() override

View File

@@ -33,10 +33,10 @@ public:
UPROPERTY(EditAnywhere, meta=(Description="Name for the new widget"))
FString Name;
UPROPERTY(meta=(Optional, Description="Parent widget name. If omitted, sets as root."))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Parent widget name. If omitted, sets as root."))
FString Parent;
UPROPERTY(meta=(Optional, Description="Whether to expose the widget as a variable in the blueprint (default false)"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Whether to expose the widget as a variable in the blueprint (default false)"))
bool IsVariable = false;
virtual void Register() override

View File

@@ -20,7 +20,7 @@ public:
UPROPERTY(EditAnywhere, meta=(Description="Query string, can contain *"))
FString Query;
UPROPERTY(meta=(Optional, Description="Maximum number of results (default 50)"))
UPROPERTY(EditAnywhere, meta=(Optional, Description="Maximum number of results (default 50)"))
int32 MaxResults = 50;
virtual void Register() override

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::GetVisibleNames(FactoryClass);
}
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

@@ -1,5 +1,6 @@
#include "WingFetcher.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingUtils.h"
#include "WingActorComponent.h"
#include "Engine/Blueprint.h"
@@ -29,6 +30,7 @@ WingFetcher::WalkFunc WingFetcher::GetWalker(const FString& Step)
if (Step.Equals(TEXT("component"), ESearchCase::IgnoreCase)) return &WingFetcher::Component;
if (Step.Equals(TEXT("widget"), ESearchCase::IgnoreCase)) return &WingFetcher::Widget;
if (Step.Equals(TEXT("levelblueprint"), ESearchCase::IgnoreCase)) return &WingFetcher::LevelBlueprint;
if (Step.Equals(TEXT("structprop"), ESearchCase::IgnoreCase)) return &WingFetcher::StructProp;
return nullptr;
}
@@ -139,18 +141,16 @@ WingFetcher& WingFetcher::Asset(const FString& PackagePath)
return SetError();
}
// Check if the package exists before calling LoadObject, because
// LoadObject logs its own errors when the package doesn't exist.
FString PackageName = FPackageName::ObjectPathToPackageName(PackagePath);
if (!FPackageName::DoesPackageExist(PackageName))
UPackage* Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn);
if (!Package)
{
Errors.Printf(TEXT("ERROR: Asset '%s' does not exist.\n"), *PackagePath);
return SetError();
}
SetObj(LoadObject<UObject>(nullptr, *PackagePath));
SetObj(Package->FindAssetInPackage());
if (!Obj)
{
Errors.Printf(TEXT("ERROR: Could not load asset '%s'\n"), *PackagePath);
Errors.Printf(TEXT("ERROR: Could not find asset in package '%s'\n"), *PackagePath);
return SetError();
}
@@ -372,3 +372,50 @@ WingFetcher& WingFetcher::LevelBlueprint(const FString& Value)
SetObj(LevelBP);
return *this;
}
WingFetcher& WingFetcher::StructProp(const FString& Value)
{
if (bError) return *this;
FName InternalID = WingUtils::CheckInternalizeID(Value, Errors);
if (InternalID.IsNone()) return SetError();
if (!Obj)
{
TypeMismatch(TEXT("structprop"), TEXT("UObject"));
return SetError();
}
FStructProperty* StructProp = nullptr;
// The "host" is the object containing this property.
UObject *HostObject = Obj;
void *HostBase = Obj;
UStruct *HostType = Obj->GetClass();
bool HostEditable = true;
// If we are *already* inside a UWingStructPointer, update the host
// fields, to make it possible to navigate even further inside.
if (UWingStructPointer *SPtr = ::Cast<UWingStructPointer>(Obj))
{
HostObject = SPtr->Object;
HostBase = SPtr->StructBase;
HostType = SPtr->StructType;
HostEditable = SPtr->Editable;
}
StructProp = FindFProperty<FStructProperty>(HostType, InternalID);
if (!StructProp)
{
Errors.Printf(TEXT("ERROR: No struct property '%s' found on %s\n"), *Value, *HostType->GetName());
return SetError();
}
UWingStructPointer* Ptr = NewObject<UWingStructPointer>();
Ptr->Object = HostObject;
Ptr->StructType = StructProp->Struct;
Ptr->StructBase = StructProp->ContainerPtrToValuePtr<void>(HostBase);
Ptr->Editable = HostEditable && StructProp->HasAllPropertyFlags(CPF_Edit);
SetObj(Ptr);
return *this;
}

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, 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

@@ -8,7 +8,7 @@ void WingManual::PrintHandlerPrototype(const FWingHandlerConfig& Handler)
WingOut::Stdout.Print(Handler.Name);
WingOut::Stdout.Print(TEXT("("));
bool bFirst = true;
for (TFieldIterator<FProperty> PropIt(Handler.Class.Get(), EFieldIterationFlags::None); PropIt; ++PropIt)
for (TFieldIterator<FProperty> PropIt(Handler.HandlerClass.Get(), EFieldIterationFlags::None); PropIt; ++PropIt)
{
if (!bFirst) WingOut::Stdout.Print(TEXT(","));
bFirst = false;
@@ -21,7 +21,7 @@ void WingManual::PrintHandlerPrototype(const FWingHandlerConfig& Handler)
void WingManual::PrintHandlerArguments(const FWingHandlerConfig& Handler)
{
// parameter details
for (TFieldIterator<FProperty> PropIt(Handler.Class.Get(), EFieldIterationFlags::None); PropIt; ++PropIt)
for (TFieldIterator<FProperty> PropIt(Handler.HandlerClass.Get(), EFieldIterationFlags::None); PropIt; ++PropIt)
{
FProperty* Prop = *PropIt;
FString Name = Prop->GetName();
@@ -306,7 +306,7 @@ void WingManual::Commands(EWingHandlerKind Kind, const FString& Query, bool Verb
// Blank line between groups
if (!Verbose)
{
FString Group = WingUtils::GetHandlerGroup(H.Class.Get());
FString Group = WingUtils::GetHandlerGroup(H.HandlerClass.Get());
if (Group != PrevGroup)
{
if (!PrevGroup.IsEmpty())

View File

@@ -1,12 +1,13 @@
#include "WingMaterialParameter.h"
#include "WingUtils.h"
#include "WingTokenizer.h"
#include "WingServer.h"
TMap<FMaterialParameterInfo, FMaterialParameterMetadata> WingMaterialParameter::GetMaterialParameters(UMaterialInterface* Material)
WingMaterialParameter::InfoMetaMap WingMaterialParameter::GetMaterialParameters(UMaterialInterface* Material)
{
TMap<FMaterialParameterInfo, FMaterialParameterMetadata> Result;
InfoMetaMap Result;
if (!Material) return Result;
TMap<FMaterialParameterInfo, FMaterialParameterMetadata> Temp;
InfoMetaMap Temp;
for (int32 i = 0; i < (int32)EMaterialParameterType::NumRuntime; i++)
{
Material->GetAllParametersOfType((EMaterialParameterType)i, Temp);
@@ -31,48 +32,183 @@ bool WingMaterialParameter::ParseMaterialParameterAssociation(const FString& Str
return true;
}
void WingMaterialParameter::FormatMaterialParameter(const FMaterialParameterInfo& Info, const FMaterialParameterMetadata& Meta)
void WingMaterialParameter::ReportBadToken(WingTokenizer &Tok, const TCHAR *Expected, WingOut Errors)
{
// Association prefix for layer/blend parameters.
FString Prefix;
if (Info.Association == LayerParameter)
Prefix = FString::Printf(TEXT("[Layer %d] "), Info.Index);
else if (Info.Association == BlendParameter)
Prefix = FString::Printf(TEXT("[Blend %d] "), Info.Index);
FString Whole = Tok.GetRange(FName(), 1000);
Tok.SaveCursor(FName());
FString Next = Tok.GetRange(FName(), 1);
Errors.Printf(TEXT("Parsing %s, near %s: expected %s"), *Whole, *Next, Expected);
}
switch (Meta.Value.Type)
FString WingMaterialParameter::StringID(const FMaterialParameterInfo &ID)
{
FString Ext = WingUtils::ExternalizeID(ID.Name);
if (ID.Association != EMaterialParameterAssociation::GlobalParameter)
{
case EMaterialParameterType::Scalar:
WingOut::Stdout.Printf(TEXT(" %sScalar \"%s\" = %g\n"), *Prefix, *Info.Name.ToString(), Meta.Value.AsScalar());
break;
case EMaterialParameterType::Vector:
{
FLinearColor C = Meta.Value.AsLinearColor();
WingOut::Stdout.Printf(TEXT(" %sVector \"%s\" = (R=%.3f, G=%.3f, B=%.3f, A=%.3f)\n"),
*Prefix, *Info.Name.ToString(), C.R, C.G, C.B, C.A);
break;
const TCHAR *Prefix = TEXT("Layer");
if (ID.Association == EMaterialParameterAssociation::BlendParameter)
Prefix = TEXT("Blend");
return FString::Printf(TEXT("%s:%d:%s"), Prefix, ID.Index, *Ext);
}
case EMaterialParameterType::DoubleVector:
else
{
FVector4d V = Meta.Value.AsVector4d();
WingOut::Stdout.Printf(TEXT(" %sDoubleVector \"%s\" = (%.3f, %.3f, %.3f, %.3f)\n"),
*Prefix, *Info.Name.ToString(), V.X, V.Y, V.Z, V.W);
break;
}
case EMaterialParameterType::Texture:
{
UTexture* Tex = Cast<UTexture>(Meta.Value.AsTextureObject());
WingOut::Stdout.Printf(TEXT(" %sTexture \"%s\" = %s\n"),
*Prefix, *Info.Name.ToString(), Tex ? *WingUtils::FormatName(Tex) : TEXT("None"));
break;
}
case EMaterialParameterType::StaticSwitch:
WingOut::Stdout.Printf(TEXT(" %sStaticSwitch \"%s\" = %s\n"),
*Prefix, *Info.Name.ToString(), Meta.Value.AsStaticSwitch() ? TEXT("true") : TEXT("false"));
break;
default:
WingOut::Stdout.Printf(TEXT(" %sType%d \"%s\"\n"), *Prefix, (int)Meta.Value.Type, *Info.Name.ToString());
break;
return Ext;
}
}
bool WingMaterialParameter::ParseID(const FString& IDString, FMaterialParameterInfo& ID, WingOut Errors)
{
WingTokenizer Tok(IDString);
ID.Association = EMaterialParameterAssociation::GlobalParameter;
ID.Index = 0;
ID.Name = FName();
// Parse the Layer or blend prefix.
if (Tok.TokenIs(TEXT("Layer")) || Tok.TokenIs(TEXT("Blend")))
{
if (Tok.NextName() == TEXT("Layer"))
ID.Association = EMaterialParameterAssociation::LayerParameter;
else
ID.Association = EMaterialParameterAssociation::BlendParameter;
Tok.Advance();
if (!Tok.TokenIs(':'))
{ ReportBadToken(Tok, TEXT("colon"), Errors); return false; }
Tok.Advance();
if ((!Tok.TokenIs(Tok.Identifier)) ||
(!LexTryParseString(ID.Index, *Tok.NextName().ToString())))
{ ReportBadToken(Tok, TEXT("integer"), Errors); return false; }
Tok.Advance();
if ((ID.Index < 0)||(ID.Index > 50))
{
Errors.Printf(TEXT("Layer/Blend index outside of reasonable range\n"));
return false;
}
if (!Tok.TokenIs(':'))
{ ReportBadToken(Tok, TEXT("colon"), Errors); return false; }
Tok.Advance();
}
// Parse the parameter name
if (!Tok.TokenIs(Tok.Identifier))
{ ReportBadToken(Tok, TEXT("parameter name"), Errors); return false; }
ID.Name = Tok.NextName();
Tok.Advance();
if (!Tok.TokenIs(0))
{ ReportBadToken(Tok, TEXT("end of input"), Errors); return false; }
return true;
}
FMaterialParameterMetadata *WingMaterialParameter::FindParameter(
InfoMetaMap &Map, const FMaterialParameterInfo &ID, WingOut Errors)
{
FMaterialParameterMetadata* Found = Map.Find(ID);
if (!Found)
{
Errors.Printf(TEXT("No parameter named '%s' in this material"), *StringID(ID));
return nullptr;
}
return Found;
}
bool WingMaterialParameter::CheckNoCustomPrimitiveData(
const FMaterialParameterInfo &ID, const FMaterialParameterMetadata &Data, WingOut Errors)
{
if (Data.PrimitiveDataIndex != INDEX_NONE)
{
Errors.Printf(
TEXT("Parameter '%s' uses custom primitive data, which cannot be set on a material instance"),
*StringID(ID));
return false;
}
return true;
}
FString WingMaterialParameter::FormatValue(const FMaterialParameterValue& Value)
{
switch (Value.Type)
{
case EMaterialParameterType::Scalar: {
return FString::Printf(TEXT("scalar(%g)"), Value.AsScalar());
}
case EMaterialParameterType::Vector: {
return FString::Printf(TEXT("vector(%s)"), *Value.AsLinearColor().ToString());
}
case EMaterialParameterType::DoubleVector: {
return FString::Printf(TEXT("doublevector(%s)"), *Value.AsLinearColor().ToString());
}
case EMaterialParameterType::Texture: {
UTexture* Tex = Cast<UTexture>(Value.AsTextureObject());
return FString::Printf(TEXT("texture(%s)"), *Tex->GetPathName());
}
case EMaterialParameterType::StaticSwitch: {
const TCHAR *Str = Value.AsStaticSwitch() ? TEXT("true") : TEXT("false");
return FString::Printf(TEXT("staticswitch(%s)"), Str);
}
default: {
return FString::Printf(TEXT("unknown(%d)"), (int)Value.Type);
}
}
}
bool WingMaterialParameter::RemoveOverride(
const FMaterialParameterInfo &ID, UMaterialInstanceConstant *MI, WingOut Errors)
{
// Remove the override from all parameter arrays.
auto RemoveFrom = [&](auto& Arr) {
return Arr.RemoveAll([&](auto& Entry) { return Entry.ParameterInfo == ID; });
};
int32 Removed = 0;
Removed += RemoveFrom(MI->ScalarParameterValues);
Removed += RemoveFrom(MI->VectorParameterValues);
Removed += RemoveFrom(MI->DoubleVectorParameterValues);
Removed += RemoveFrom(MI->TextureParameterValues);
Removed += RemoveFrom(MI->TextureCollectionParameterValues);
Removed += RemoveFrom(MI->RuntimeVirtualTextureParameterValues);
Removed += RemoveFrom(MI->SparseVolumeTextureParameterValues);
Removed += RemoveFrom(MI->FontParameterValues);
if (Removed == 0)
{
Errors.Printf(TEXT("No override found for parameter '%s'"), *StringID(ID));
return false;
}
return true;
}
void WingMaterialParameter::Print(
const FMaterialParameterInfo &Info, const FMaterialParameterMetadata &Meta)
{
WingOut::Stdout.Printf(TEXT(" %s %s\n"),
*WingMaterialParameter::StringID(Info), *WingMaterialParameter::FormatValue(Meta.Value));
}
void WingMaterialParameter::PrintAll(const InfoMetaMap &Params, bool Headings)
{
// Overridden parameters first.
bool bHasOverrides = false;
for (auto& [Info, Meta] : Params)
{
if (!Meta.bOverride) continue;
if (Headings && !bHasOverrides)
{ WingOut::Stdout.Print(TEXT("\nOverridden Parameters:\n")); bHasOverrides = true; }
WingMaterialParameter::Print(Info, Meta);
}
// Inherited (non-overridden) parameters.
bool bHasInherited = false;
for (auto& [Info, Meta] : Params)
{
if (Meta.bOverride) continue;
if (Headings && !bHasInherited)
{ WingOut::Stdout.Print(TEXT("\nInherited Parameters (not overridden):\n")); bHasInherited = true; }
WingMaterialParameter::Print(Info, Meta);
}
}

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

@@ -16,6 +16,7 @@
bool FWingProperty::SetObject(UObject *Obj, WingOut Errors) const
{
if (!CheckEditable(Errors)) return false;
FObjectPropertyBase *OProp = CastField<FObjectPropertyBase>(Prop);
if (!OProp)
{
@@ -45,94 +46,88 @@ bool FWingProperty::SetObject(UObject *Obj, WingOut Errors) const
return true;
}
bool FWingProperty::SetDoubleInternal(
FNumericProperty *NProp, void *Container, double D, WingOut Errors)
{
uint8 buffer[16];
NProp->SetFloatingPointPropertyValue(buffer, D);
if (!FMath::IsFinite(NProp->GetFloatingPointPropertyValue(buffer)))
{
Errors.Printf(TEXT("ERROR: Property '%s' of type %s cannot hold %lf\n"),
*WingUtils::FormatName(NProp), *NProp->GetCPPType(), D);
return false;
}
NProp->SetValue_InContainer(Container, buffer);
return true;
}
bool FWingProperty::SetInt64Internal(
FNumericProperty *NProp, void *Container, int64 I, WingOut Errors)
{
uint8 buffer[16];
if ((I < 0) && IsUnsigned(NProp))
{
Errors.Printf(TEXT(
"ERROR: Cannot store signed %lld in unsigned property %s\n"),
I, *WingUtils::FormatName(NProp));
return false;
}
NProp->SetIntPropertyValue(buffer, I);
if (NProp->GetSignedIntPropertyValue(buffer) != I)
{
Errors.Printf(TEXT("ERROR: Property '%s' of type %s cannot hold %lld\n"),
*WingUtils::FormatName(NProp), *NProp->GetCPPType(), I);
return false;
}
NProp->SetValue_InContainer(Container, buffer);
return true;
}
bool FWingProperty::SetDouble(double D, WingOut Errors) const
{
if (!CheckEditable(Errors)) return false;
FNumericProperty *NProp = CastField<FNumericProperty>(Prop);
if (!NProp)
if (NProp == nullptr)
{
PrintExpectsReceived(TEXT("double"), Errors);
return false;
}
if (NProp->IsFloatingPoint())
else if (NProp->IsFloatingPoint())
{
uint8 buffer[16];
NProp->SetFloatingPointPropertyValue(buffer, D);
if (!FMath::IsFinite(NProp->GetFloatingPointPropertyValue(buffer)))
{
Errors.Printf(TEXT("ERROR: Property '%s' of type %s cannot hold %lf\n"),
*WingUtils::FormatName(Prop), *Prop->GetCPPType(), D);
return false;
}
Prop->SetValue_InContainer(Container, buffer);
return true;
return SetDoubleInternal(NProp, Container, D, Errors);
}
else
{
uint8 buffer[16];
if (FMath::Floor(D) != D)
{
PrintExpectsReceived(TEXT("double"), Errors);
return false;
}
if (FMath::Abs(D) > (double)((1LL)<<53))
{
Errors.Printf(TEXT("ERROR: To store very large numbers in '%s', do not pass them as double. Use string or int.\n"),
*WingUtils::FormatName(Prop));
return false;
}
int64 I = (int64)D;
NProp->SetIntPropertyValue(buffer, I);
if (NProp->GetSignedIntPropertyValue(buffer) != I)
{
Errors.Printf(TEXT("ERROR: Property '%s' of type %s cannot hold %lld\n"),
*WingUtils::FormatName(Prop), *Prop->GetCPPType(), I);
return false;
}
NProp->SetValue_InContainer(Container, buffer);
return true;
int64 I;
if (!LosslessDoubleToInt64(D, I, Errors)) return false;
return SetInt64Internal(NProp, Container, I, Errors);
}
}
bool FWingProperty::SetInt64(int64 I, WingOut Errors) const
{
if (!CheckEditable(Errors)) return false;
FNumericProperty *NProp = CastField<FNumericProperty>(Prop);
if (!NProp)
if (NProp == nullptr)
{
PrintExpectsReceived(TEXT("int"), Errors);
return false;
}
if (NProp->IsFloatingPoint())
else if (NProp->IsFloatingPoint())
{
uint8 buffer[16];
double D = I;
NProp->SetFloatingPointPropertyValue(buffer, D);
int64 RT = (int64)NProp->GetFloatingPointPropertyValue(buffer);
if (RT != I)
{
Errors.Printf(TEXT("ERROR: Property '%s' of type %s cannot hold %lld\n"),
*WingUtils::FormatName(Prop), *Prop->GetCPPType(), I);
return false;
}
Prop->SetValue_InContainer(Container, buffer);
return true;
double D;
if (!LosslessInt64ToDouble(I, D, Errors)) return false;
return SetDoubleInternal(NProp, Container, D, Errors);
}
else
{
uint8 buffer[16];
NProp->SetIntPropertyValue(buffer, I);
if (NProp->GetSignedIntPropertyValue(buffer) != I)
{
Errors.Printf(TEXT("ERROR: Property '%s' of type %s cannot hold %lld\n"),
*WingUtils::FormatName(Prop), *Prop->GetCPPType(), I);
return false;
}
NProp->SetValue_InContainer(Container, buffer);
return true;
return SetInt64Internal(NProp, Container, I, Errors);
}
}
bool FWingProperty::SetBool(bool B, WingOut Errors) const
{
if (!CheckEditable(Errors)) return false;
if (FBoolProperty* BoolProp = CastField<FBoolProperty>(Prop))
{
Prop->SetValue_InContainer(Container, &B);
@@ -144,6 +139,8 @@ bool FWingProperty::SetBool(bool B, WingOut Errors) const
bool FWingProperty::SetText(FString Value, WingOut Errors) const
{
if (!CheckEditable(Errors)) return false;
// Pin types get parsed by UWingTypes.
if (IsPinTypeProperty(Prop))
{
@@ -157,6 +154,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.
@@ -186,6 +198,8 @@ bool FWingProperty::SetText(FString Value, WingOut Errors) const
bool FWingProperty::SetJson(const FJsonValue &JsonValue, WingOut Errors) const
{
if (!CheckEditable(Errors)) return false;
if (JsonValue.Type == EJson::String)
{
return SetText(JsonValue.AsString(), Errors);
@@ -322,6 +336,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;
@@ -338,6 +358,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"));
@@ -345,28 +375,35 @@ FString FWingProperty::GetCategory() const
return Result;
}
void FWingProperty::GetAll(FWingStructAndUStruct Obj, EPropertyFlags Flags, TArray<FWingProperty> &Props)
{
for (TFieldIterator<FProperty> It(Obj.UStructPtr); It; ++It)
{
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
Props.Add(FWingProperty(*It, Obj.StructPtr));
}
}
TArray<FWingProperty> FWingProperty::GetAll(FWingStructAndUStruct Obj, EPropertyFlags Flags)
TArray<FWingProperty> FWingProperty::GetAll(FWingStructAndUStruct Obj)
{
TArray<FWingProperty> Result;
GetAll(Obj, Flags, Result);
for (TFieldIterator<FProperty> It(Obj.UStructPtr); It; ++It)
{
bool Editable = !It->HasAnyPropertyFlags(CPF_EditConst);
Result.Add(FWingProperty(*It, Obj.StructPtr, Editable));
}
return Result;
}
TArray<FName> FWingProperty::GetNames(UStruct *US, EPropertyFlags Flags)
TArray<FWingProperty> FWingProperty::GetVisible(FWingStructAndUStruct Obj)
{
TArray<FWingProperty> Result;
for (TFieldIterator<FProperty> It(Obj.UStructPtr); It; ++It)
{
if (!It->HasAllPropertyFlags(CPF_Edit)) continue;
bool Editable = !It->HasAnyPropertyFlags(CPF_EditConst);
Result.Add(FWingProperty(*It, Obj.StructPtr, Editable));
}
return Result;
}
TArray<FName> FWingProperty::GetVisibleNames(UStruct *US)
{
TArray<FName> Result;
for (TFieldIterator<FProperty> It(US); It; ++It)
{
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
if (!It->HasAllPropertyFlags(CPF_Edit)) continue;
Result.Add(It->GetFName());
}
return Result;
@@ -388,10 +425,20 @@ void FWingProperty::Move(TArray<FWingProperty> &Out, TArray<FWingProperty> &In,
In.SetNum(Dst);
}
TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, EPropertyFlags Flags, bool Mutable)
TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, bool Mutable)
{
if (!Obj) return {};
// If it's a UWingStructPointer, return the properties
// of the struct instead. Propagate editability of the host.
if (UWingStructPointer *SP = Cast<UWingStructPointer>(Obj))
{
TArray<FWingProperty> Result =
GetVisible(FWingStructAndUStruct(SP->StructBase, SP->StructType));
if (!Mutable || (!SP->Editable)) StripEditable(Result);
return Result;
}
// Blueprints don't have editable properties. So
// instead, we fetch properties from the generated CDO.
if (UBlueprint *BP = ::Cast<UBlueprint>(Obj))
@@ -415,7 +462,7 @@ TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, EPropertyFlags Fla
}
}
TArray<FWingProperty> Result = GetAll(Obj, Flags);
TArray<FWingProperty> Result = GetVisible(Obj);
// If it's a Material Graph node, also collect properties from
// the associated material expression.
@@ -424,7 +471,7 @@ TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, EPropertyFlags Fla
{
if (UMaterialExpression* Expr = MatNode->MaterialExpression)
{
GetAll(Expr, Flags, Result);
Result.Append(GetVisible(Expr));
}
}
@@ -435,10 +482,11 @@ TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, EPropertyFlags Fla
FWingProperty::Remove(Result, TEXT("Slot"));
if (UPanelSlot* Slot = Widget->Slot)
{
GetAll(Slot, Flags, Result);
Result.Append(GetVisible(Slot));
}
}
if (!Mutable) StripEditable(Result);
return Result;
}
@@ -524,4 +572,59 @@ bool FWingProperty::CheckImportTextResult(const FString &Value, WingOut Errors)
}
}
return true;
}
}
void FWingProperty::StripEditable(TArray<FWingProperty> &Props)
{
for (FWingProperty &Elt : Props) Elt.Editable = false;
}
bool FWingProperty::CheckEditable(WingOut Errors) const
{
if (!Editable)
{
Errors.Printf(TEXT("ERROR: Cannot edit property %s, not marked editable"),
*WingUtils::FormatName(Prop));
return false;
}
return true;
}
bool FWingProperty::IsUnsigned(FNumericProperty* Prop)
{
return
CastField<FByteProperty>(Prop) ||
CastField<FUInt16Property>(Prop) ||
CastField<FUInt32Property>(Prop) ||
CastField<FUInt64Property>(Prop);
}
bool FWingProperty::LosslessDoubleToInt64(double D, int64 &I, WingOut Errors)
{
if (FMath::Floor(D) != D)
{
Errors.Printf(TEXT(
"ERROR: Converting double %.4lf to integer would lose precision\n"), D);
return false;
}
if (FMath::Abs(D) > (double)((1LL)<<53))
{
Errors.Printf(TEXT(
"ERROR: Converting huge double %lf to integer would lose data.\n"), D);
return false;
}
I = (int64)D;
return true;
}
bool FWingProperty::LosslessInt64ToDouble(int64 I, double &D, WingOut Errors)
{
D = (double)I;
if ((int64)D != I)
{
Errors.Printf(TEXT(
"ERROR: Converting huge integer %lld to floating point would lose data.\n"), I);
return false;
}
return true;
}

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();
@@ -323,12 +326,12 @@ void UWingServer::TryCallHandler(const FString &Line)
LastHandler = Found;
// Make an object of the handler class.
TStrongObjectPtr<UObject> HandlerObj(NewObject<UObject>(GetTransientPackage(), Found->Class.Get()));
TStrongObjectPtr<UObject> HandlerObj(NewObject<UObject>(GetTransientPackage(), Found->HandlerClass.Get()));
UWingHandler* Handler = Cast<UWingHandler>(HandlerObj.Get());
Handler->ConfigurationObject = Found->Config.Get();
Handler->Configuration = Found;
// Populate the handler object with the request parameters.
TArray<FWingProperty> Props = FWingProperty::GetAll(Handler, CPF_Edit);
TArray<FWingProperty> Props = FWingProperty::GetVisible(Handler);
if (!FWingProperty::PopulateFromJson(Props, *Request, false, WingOut::Stdout))
{
UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
@@ -362,8 +365,6 @@ void UWingServer::AcceptNewConnections()
Client->Socket = ClientSocket;
Client->ThreadFuture = Async(EAsyncExecution::Thread, [this, Client]() { ClientThreadFunc(this, Client); });
Clients.Add(Client);
UE_LOG(LogTemp, Display, TEXT("UEWingman: Client connected."));
}
void UWingServer::CleanupFinishedClients()
@@ -383,71 +384,156 @@ void UWingServer::CleanupFinishedClients()
}
}
// ============================================================
// Stuff Performed on the Client Thread
// ============================================================
void UWingServer::ClientThreadFunc(UWingServer* Server, TSharedPtr<FClientConnection> Client)
{
constexpr int32 MaxRecvBufBytes = 1024 * 1024;
constexpr int32 MinUnusedRecvSpace = 4096;
FSocket* Socket = Client->Socket;
FString LineBuffer;
uint8 RecvBuf[4096];
TArray<uint8> RecvBuf;
RecvBuf.SetNumUninitialized(MinUnusedRecvSpace);
int32 RecvLen = 0;
WaitForAssetRegistry();
while (true)
{
int32 BytesRead = 0;
if (!Socket->Recv(RecvBuf, sizeof(RecvBuf) - 1, BytesRead))
FString Request;
if (ExtractRequestFromBuffer(RecvBuf, RecvLen, Request))
{
break; // socket error or closed
}
if (BytesRead <= 0)
{
break; // connection closed
}
RecvBuf[BytesRead] = 0;
LineBuffer += UTF8_TO_TCHAR((const ANSICHAR*)RecvBuf);
// Process complete lines
int32 NewlineIdx;
while (LineBuffer.FindChar(TEXT('\n'), NewlineIdx))
{
FString Line = LineBuffer.Left(NewlineIdx).TrimEnd();
LineBuffer.RightChopInline(NewlineIdx + 1);
if (Line.IsEmpty()) continue;
// Wait for the asset registry to finish its initial scan.
FString Response;
if (!ProcessRequestOnGameThread(Request, Response))
{
IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
while (AR.IsLoadingAssets()) FPlatformProcess::Sleep(0.25f);
Client->bDone = true;
return;
}
// Enqueue the line for game-thread processing
TSharedPtr<UWingServer::FPendingMessage> Msg = MakeShared<UWingServer::FPendingMessage>();
Msg->Line = Line;
TFuture<FString> Future = Msg->Response.GetFuture();
{
FScopeLock Lock(&Server->Mutex);
if (Server->bShuttingDown)
{
Client->bDone = true;
return;
}
Server->PendingMessages.Add(Msg);
}
// Block until the game thread processes this message
FString Response = Future.Get();
// Write the response back, null-terminated (blocking)
FTCHARToUTF8 Utf8(*Response);
int32 BytesSent = 0;
Socket->Send((const uint8*)Utf8.Get(), Utf8.Length() + 1, BytesSent);
if (!SendAll(Socket, reinterpret_cast<const uint8*>(Utf8.Get()),
Utf8.Length() + 1))
{
Client->bDone = true;
return;
}
continue;
}
if (!ReceiveMoreBytesIntoBuffer(Socket, RecvBuf, RecvLen))
{
break;
}
}
Client->bDone = true;
UE_LOG(LogTemp, Display, TEXT("UEWingman: Client disconnected."));
}
bool UWingServer::ExtractRequestFromBuffer(
TArray<uint8>& RecvBuf, int32& RecvLen, FString& OutRequest)
{
const uint8* EndOfRequest = static_cast<const uint8*>(
memchr(RecvBuf.GetData(), '\0', RecvLen));
if (EndOfRequest == nullptr)
{
return false;
}
const int32 MessageLen =
static_cast<int32>(EndOfRequest - RecvBuf.GetData());
OutRequest = FString::ConstructFromPtrSize(
reinterpret_cast<const UTF8CHAR*>(RecvBuf.GetData()), MessageLen);
const int32 RemainingBytes = RecvLen - (MessageLen + 1);
if (RemainingBytes > 0)
{
FMemory::Memmove(
RecvBuf.GetData(),
RecvBuf.GetData() + MessageLen + 1,
RemainingBytes);
}
RecvLen = RemainingBytes;
return true;
}
bool UWingServer::ReceiveMoreBytesIntoBuffer(
FSocket* Socket, TArray<uint8>& RecvBuf, int32& RecvLen)
{
constexpr int32 MaxRecvBufBytes = 1024 * 1024;
constexpr int32 MinUnusedRecvSpace = 4096;
int32 UnusedSpace = RecvBuf.Num() - RecvLen;
if (UnusedSpace < MinUnusedRecvSpace)
{
if (RecvBuf.Num() >= MaxRecvBufBytes)
{
return false;
}
RecvBuf.SetNumUninitialized(RecvBuf.Num() * 2);
UnusedSpace = RecvBuf.Num() - RecvLen;
}
int32 BytesRead = 0;
if (!Socket->Recv(RecvBuf.GetData() + RecvLen, UnusedSpace, BytesRead))
{
return false;
}
if (BytesRead <= 0)
{
return false;
}
RecvLen += BytesRead;
return true;
}
bool UWingServer::SendAll(FSocket* Socket, const uint8* Data, int32 BytesToSend)
{
while (BytesToSend > 0)
{
int32 BytesSent = 0;
if (!Socket->Send(Data, BytesToSend, BytesSent) || (BytesSent <= 0))
{
return false;
}
Data += BytesSent;
BytesToSend -= BytesSent;
}
return true;
}
bool UWingServer::ProcessRequestOnGameThread(
const FString& Request, FString& Response)
{
// Enqueue the message for game-thread processing.
TSharedPtr<UWingServer::FPendingMessage> Msg =
MakeShared<UWingServer::FPendingMessage>();
Msg->Line = Request;
TFuture<FString> Future = Msg->Response.GetFuture();
{
FScopeLock Lock(&GWingServer->Mutex);
if (GWingServer->bShuttingDown)
{
return false;
}
GWingServer->PendingMessages.Add(Msg);
}
// Block until the game thread processes this message.
Response = Future.Get();
return true;
}
void UWingServer::WaitForAssetRegistry()
{
IAssetRegistry& AR =
FModuleManager::LoadModuleChecked<FAssetRegistryModule>(
"AssetRegistry").Get();
while (AR.IsLoadingAssets()) FPlatformProcess::Sleep(0.25f);
}
// ============================================================
// BuildWingHandlerRegistry
@@ -455,16 +541,17 @@ void UWingServer::ClientThreadFunc(UWingServer* Server, TSharedPtr<FClientConnec
void UWingServer::AddHandler(UObject* Obj, const FString& Documentation)
{
AddHandler(Obj, WingUtils::GetHandlerName(Obj->GetClass()), nullptr, EWingHandlerKind::Normal, Documentation);
AddHandler(Obj, WingUtils::GetHandlerName(Obj->GetClass()), nullptr, EWingHandlerKind::Normal, nullptr, Documentation);
}
void UWingServer::AddHandler(UObject* Obj, const FString& Name, UObject* Config, EWingHandlerKind Kind, const FString& Documentation)
void UWingServer::AddHandler(UObject* Obj, const FString& Name, UObject* Config, EWingHandlerKind Kind, UClass* FactoryClass, const FString& Documentation)
{
FWingHandlerConfig H;
H.Name = Name;
H.Documentation = Documentation;
H.Class = TStrongObjectPtr<UClass>(Obj->GetClass());
H.HandlerClass = TStrongObjectPtr<UClass>(Obj->GetClass());
H.Config = TStrongObjectPtr<UObject>(Config);
H.FactoryClass = TStrongObjectPtr<UClass>(FactoryClass);
H.Kind = Kind;
GWingServer->WingHandlerRegistry.Add(MoveTemp(H));
@@ -472,6 +559,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 +568,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

@@ -249,16 +249,16 @@ void WingTokenizer::SaveCursor(FName Name)
SavedCursor.Emplace(Name, Cursor);
}
FStringView WingTokenizer::GetRange(FName SavePos, int Extra) const
FString WingTokenizer::GetRange(FName SavePos, int Extra) const
{
int Lo = 0;
for (auto &Pair : SavedCursor) if (Pair.Key == SavePos) Lo = Pair.Value;
int Hi = (Next - Tokens.GetData()) + Extra;
Hi = FMath::Clamp(Hi, Lo, Tokens.Num());
if (Lo >= Hi) return FStringView();
if (Lo >= Hi) return FString();
const TCHAR* Start = Tokens[Lo].Source.GetData();
const TCHAR* End = Tokens[Hi - 1].Source.GetData() + Tokens[Hi - 1].Source.Len();
return FStringView(Start, End - Start);
return FString(End - Start, Start);
}
void WingTokenizer::PrintEverything(FStringBuilderBase &Out) const

View File

@@ -12,8 +12,22 @@
#include "AssetRegistry/AssetRegistryModule.h"
#include "Blueprint/BlueprintSupport.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "GameFramework/Actor.h"
#include "Components/ActorComponent.h"
#include "Blueprint/UserWidget.h"
#include "GameFramework/Pawn.h"
#include "Engine/DataAsset.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
// ---------------------------------------------------------------------------
@@ -128,6 +142,22 @@ const UClass *UWingTypes::FindNativeParent(const UClass *Obj)
return Native;
}
FName UWingTypes::BroadCategory(const UClass* Class)
{
// The nullptr case can really only happen if metadata is missing.
//
if (Class == nullptr) return("Unknown");
// Classify the object type.
//
if (Class->IsChildOf(UUserWidget::StaticClass())) return TEXT("Widget");
if (Class->IsChildOf(UActorComponent::StaticClass())) return TEXT("ActorComponent");
if (Class->IsChildOf(APawn::StaticClass())) return TEXT("Pawn");
if (Class->IsChildOf(AActor::StaticClass())) return TEXT("Actor");
if (Class->IsChildOf(UDataAsset::StaticClass())) return TEXT("DataAsset");
return TEXT("Object");
}
// ---------------------------------------------------------------------------
// Choose Short Name
// ---------------------------------------------------------------------------
@@ -140,7 +170,7 @@ void UWingTypes::ReserveShortName(FName Name, FName PinCategory, FName PinSubCat
Dummy.PinCategory = PinCategory;
Dummy.PinSubCategory = PinSubCategory;
Dummy.PinSubCategoryObject.Empty();
Dummy.NativeParent = nullptr;
Dummy.BroadCategory = TEXT("Primitive");
Dummy.IsUserDefined = false;
ShortToInfo.Add(NameStr.ToLower(), MoveTemp(Dummy));
}
@@ -157,7 +187,7 @@ FString UWingTypes::GetShortName(const FString &Path)
return FString();
}
FString UWingTypes::NewShortName(const FString &Path, FName PinCategory, const UClass *NativeParent, bool IsUserDefined)
FString UWingTypes::NewShortName(const FString &Path, FName PinCategory, FName BroadCategory, bool IsUserDefined)
{
// Verify that the path is not already associated.
check(!PathToShort.Find(Path));
@@ -178,7 +208,7 @@ FString UWingTypes::NewShortName(const FString &Path, FName PinCategory, const U
Info TypeInfo;
TypeInfo.PinCategory = PinCategory;
TypeInfo.PinSubCategoryObject = Path;
TypeInfo.NativeParent = NativeParent;
TypeInfo.BroadCategory = BroadCategory;
TypeInfo.IsUserDefined = IsUserDefined;
// Check if the proposed name is available.
@@ -222,12 +252,14 @@ FString UWingTypes::ChooseShortName(const UObject* Obj)
if (Cast<UEnum>(Obj))
{
return NewShortName(Path, UEdGraphSchema_K2::PC_Enum, nullptr, Cast<UUserDefinedEnum>(Obj) != nullptr);
return NewShortName(Path, UEdGraphSchema_K2::PC_Enum, TEXT("Enum"),
Cast<UUserDefinedEnum>(Obj) != nullptr);
}
if (Cast<UScriptStruct>(Obj))
{
return NewShortName(Path, UEdGraphSchema_K2::PC_Struct, nullptr, Cast<UUserDefinedStruct>(Obj) != nullptr);
return NewShortName(Path, UEdGraphSchema_K2::PC_Struct, TEXT("Struct"),
Cast<UUserDefinedStruct>(Obj) != nullptr);
}
if (const UClass* Class = Cast<UClass>(Obj))
@@ -235,7 +267,7 @@ FString UWingTypes::ChooseShortName(const UObject* Obj)
bool IsInterface = Class->IsChildOf(UInterface::StaticClass());
bool IsUserDefined = (Class->ClassGeneratedBy != nullptr);
FName PinCategory = IsInterface ? UEdGraphSchema_K2::PC_Interface : UEdGraphSchema_K2::PC_Object;
return NewShortName(Path, PinCategory, FindNativeParent(Class), IsUserDefined);
return NewShortName(Path, PinCategory, BroadCategory(Class), IsUserDefined);
}
return FString();
@@ -252,7 +284,7 @@ void UWingTypes::ChooseShortName(const FAssetData &Data)
{
FString Path = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName);
if (!GetShortName(Path).IsEmpty()) return;
NewShortName(Path, UEdGraphSchema_K2::PC_Struct, nullptr, true);
NewShortName(Path, UEdGraphSchema_K2::PC_Struct, TEXT("Struct"), true);
return;
}
@@ -261,7 +293,7 @@ void UWingTypes::ChooseShortName(const FAssetData &Data)
{
FString Path = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName);
if (!GetShortName(Path).IsEmpty()) return;
NewShortName(Path, UEdGraphSchema_K2::PC_Enum, nullptr, true);
NewShortName(Path, UEdGraphSchema_K2::PC_Enum, TEXT("Enum"), true);
return;
}
@@ -285,12 +317,12 @@ void UWingTypes::ChooseShortName(const FAssetData &Data)
if (BPType == TEXT("BPTYPE_Interface"))
{
check(NativeParent->IsChildOf(UInterface::StaticClass()));
NewShortName(Path, UEdGraphSchema_K2::PC_Interface, NativeParent, true);
NewShortName(Path, UEdGraphSchema_K2::PC_Interface, TEXT("Interface"), true);
return;
}
else if (BPType == TEXT("BPTYPE_Normal") || BPType == TEXT("BPTYPE_Const"))
{
NewShortName(Path, UEdGraphSchema_K2::PC_Object, NativeParent, true);
NewShortName(Path, UEdGraphSchema_K2::PC_Object, BroadCategory(NativeParent), true);
return;
}
}
@@ -318,7 +350,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 +441,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 +449,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 +459,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 +637,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 +649,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 +681,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 +692,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 +711,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
}
}
if (Require.BlueprintType.GetValue())
if (Require.BlueprintType && (!AllowNoneIsOverriding))
{
if (!IsBlueprintType(OutPinType))
{
@@ -674,7 +722,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
}
}
if (Require.Blueprintable.GetValue())
if (Require.Blueprintable && (!AllowNoneIsOverriding))
{
if (!IsBlueprintable(OutPinType))
{
@@ -694,36 +742,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 +768,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")));
@@ -780,4 +799,3 @@ void UWingTypes::Deinitialize()
}
Super::Deinitialize();
}

View File

@@ -274,12 +274,6 @@ FName WingUtils::GetFName(const FWingProperty &Prop)
return Prop.Prop->GetFName();
}
FName WingUtils::GetFName(const TSharedPtr<IPropertyHandle> &H)
{
return H->GetProperty()->GetFName();
}
// ============================================================
// Formatting other things
// ============================================================
@@ -385,21 +379,6 @@ bool WingUtils::CheckExactlyOneNamed(int Count, const TCHAR *Kind, FName Name, W
return CheckExactlyOneNamed(Count, Kind, ExternalizeID(Name), Errors);
}
bool WingUtils::CheckExactlyNoneNamed(int Count, const TCHAR *Kind, const FString &Name, WingOut Errors)
{
if (Count > 0)
{
Errors.Printf(TEXT("A %s named %s already exists."), Kind, *Name);
return false;
}
return true;
}
bool WingUtils::CheckExactlyNoneNamed(int Count, const TCHAR *Kind, FName Name, WingOut Errors)
{
return CheckExactlyNoneNamed(Count, Kind, ExternalizeID(Name), Errors);
}
bool WingUtils::CheckCanRename(UEdGraphNode* Node, const FString &Name, WingOut Errors)
{
if (!Node->bCanRenameNode)

View File

@@ -319,7 +319,7 @@ WingVariables::Var WingVariables::LoadBlueprintVariableDescription(FBPVariableDe
FProperty* Prop = CDO->GetClass()->FindPropertyByName(Desc.VarName);
if (Prop)
{
Result.DefaultValue = FWingProperty(Prop, CDO).GetText();
Result.DefaultValue = FWingProperty(Prop, CDO, false).GetText();
Result.DefaultSpecified = true;
}
}
@@ -494,7 +494,7 @@ bool WingVariables::ModifyBlueprintDefaults(WingOut Errors)
*WingTokenizer::ExternalizeID(Input.Name));
return false;
}
if (!FWingProperty(Prop, CDO).SetText(Input.DefaultValue, Errors)) return false;
if (!FWingProperty(Prop, CDO, true).SetText(Input.DefaultValue, Errors)) return false;
}
}
return true;
@@ -538,11 +538,11 @@ bool WingVariables::ModifyEditablePinBase(WingVariableList &List, UK2Node_Editab
{
for (Var &V : List.Variables)
{
TSharedPtr<FUserPinInfo> *Found =
TSharedPtr<FUserPinInfo> Found =
WingUtils::FindOneWithInternalID(V.Name, Node->UserDefinedPins, List.ListName, Errors);
if (!Found) return false;
(*Found)->PinType = V.Type;
if (V.DefaultSpecified) (*Found)->PinDefaultValue = V.DefaultValue;
Found->PinType = V.Type;
if (V.DefaultSpecified) Found->PinDefaultValue = V.DefaultValue;
}
return true;
}
@@ -736,13 +736,13 @@ bool WingVariables::RemoveGraph(WingOut Errors)
// Verify that all named variables exist before removing anything.
for (const Var& V : InputVariables.Variables)
{
TSharedPtr<FUserPinInfo>* Found =
TSharedPtr<FUserPinInfo> Found =
WingUtils::FindOneWithInternalID(V.Name, InputNode->UserDefinedPins, TEXT("input variable"), Errors);
if (!Found) return false;
}
for (const Var& V : OutputVariables.Variables)
{
TSharedPtr<FUserPinInfo>* Found =
TSharedPtr<FUserPinInfo> Found =
WingUtils::FindOneWithInternalID(V.Name, OutputNode->UserDefinedPins, TEXT("output variable"), Errors);
if (!Found) return false;
}
@@ -778,7 +778,7 @@ bool WingVariables::RemoveCustomEvent(WingOut Errors)
// Verify that all named parameters exist before removing anything.
for (const Var& V : InputVariables.Variables)
{
TSharedPtr<FUserPinInfo>* Found =
TSharedPtr<FUserPinInfo> Found =
WingUtils::FindOneWithInternalID(V.Name, CustomEvent->UserDefinedPins, TEXT("event parameter"), Errors);
if (!Found) return false;
}

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

@@ -54,6 +54,7 @@ public:
WingFetcher& Component(const FString& Value);
WingFetcher& Widget(const FString& Value);
WingFetcher& LevelBlueprint(const FString& Value);
WingFetcher& StructProp(const FString& Value);
// Return true if there haven't been any errors.
// Note that errors always automatically generate

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

@@ -39,8 +39,9 @@ struct FWingHandlerConfig
GENERATED_BODY()
FString Name;
FString Documentation;
TStrongObjectPtr<UClass> Class;
TStrongObjectPtr<UClass> HandlerClass;
TStrongObjectPtr<UObject> Config;
TStrongObjectPtr<UClass> FactoryClass;
EWingHandlerKind Kind = EWingHandlerKind::Normal;
};
@@ -57,7 +58,7 @@ public:
virtual void Handle() {}
// The configuration object.
UObject *ConfigurationObject;
FWingHandlerConfig *Configuration;
public:
};
@@ -93,6 +94,9 @@ struct FWingStructAndUStruct
void *StructPtr;
UStruct *UStructPtr;
// Explicit constructor.
explicit FWingStructAndUStruct(void *Base, UStruct *S) : StructPtr(Base), UStructPtr(S) {}
// Copy constructor.
FWingStructAndUStruct(FWingStructAndUStruct &Src) : StructPtr(Src.StructPtr), UStructPtr(Src.UStructPtr) {}
@@ -102,4 +106,29 @@ struct FWingStructAndUStruct
// Construct from a UStruct pointer.
template<class T, typename = std::enable_if_t<!std::is_base_of_v<UObject, T>>>
FWingStructAndUStruct(T *Struct) : StructPtr(Struct), UStructPtr(Struct->StaticStruct()) {}
};
// WingStructPointer: Allows us to store a pointer to a
// ustruct in a variable of class UObject, through the magic
// of an extra level of indirection. If the struct is part of
// a UObject, then the Object field should be point at the
// object to ensure it doesn't get garbage collected. If the
// editable flag is false, it means the struct is for viewing
// only.
UCLASS()
class UWingStructPointer : public UObject
{
GENERATED_BODY()
public:
UPROPERTY()
UObject* Object;
UPROPERTY()
UStruct* StructType;
void *StructBase;
bool Editable;
};

View File

@@ -2,13 +2,63 @@
#include "CoreMinimal.h"
#include "Materials/Material.h"
#include "WingTokenizer.h"
#include "Materials/MaterialInstanceConstant.h"
#include "WingHandler.h"
class WingMaterialParameter
{
public:
static TMap<FMaterialParameterInfo, FMaterialParameterMetadata> GetMaterialParameters(UMaterialInterface* Material);
using InfoMetaMap = TMap<FMaterialParameterInfo, FMaterialParameterMetadata>;
// Get all the material parameters of the specified material including
// both parameters that have been overridden and those that haven't.
//
static InfoMetaMap GetMaterialParameters(UMaterialInterface* Material);
// Find a material parameter in the InfoMap.
// If it doesn't exist, or report an error and return nullptr.
//
static FMaterialParameterMetadata *FindParameter(
InfoMetaMap &Map, const FMaterialParameterInfo &ID, WingOut Errors);
// If the material uses custom primitive data, print an error
// and return false.
//
static bool CheckNoCustomPrimitiveData(
const FMaterialParameterInfo &ID, const FMaterialParameterMetadata &Data, WingOut Errors);
// Convert a parameter ID into an ID string.
//
static bool ParseID(const FString& Str, FMaterialParameterInfo& ID, WingOut Errors);
// Parse a parameter ID String into a parameter ID.
//
static FString StringID(const FMaterialParameterInfo& ID);
// Remove a material parameter override from a material instance.
// If there is no override, print an error and return false.
//
static bool RemoveOverride(
const FMaterialParameterInfo &ID, UMaterialInstanceConstant *MI, WingOut Errors);
// Format a material parameter value.
//
static FString FormatValue(const FMaterialParameterValue& Value);
// Print out a material parameter with ID.
//
static void Print(const FMaterialParameterInfo &Info, const FMaterialParameterMetadata &Meta);
// Print out a map full of material parameters. If Headings is
// true, the parameters are grouped by inherited/overridden.
//
static void PrintAll(const InfoMetaMap &Params, bool Headings);
static bool ParseMaterialParameterAssociation(const FString& Str, EMaterialParameterAssociation& OutAssociation, WingOut Errors);
static void FormatMaterialParameter(const FMaterialParameterInfo& Info, const FMaterialParameterMetadata& Meta);
private:
static void ReportBadToken(WingTokenizer &Tok, const TCHAR *Expected, WingOut Errors);
};

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

@@ -12,11 +12,12 @@ struct FWingProperty
FProperty* Prop = nullptr;
void* Container = nullptr;
bool Editable = false;
// Construct a property reference.
//
FWingProperty(FProperty* InProp, void* InContainer)
: Prop(InProp), Container(InContainer) {}
FWingProperty(FProperty* InProp, void* InContainer, bool Edit)
: Prop(InProp), Container(InContainer), Editable(Edit) {}
// Construct a null property reference.
//
@@ -30,8 +31,7 @@ struct FWingProperty
// error. These will do automatic conversion of numeric
// types to other numeric types, as long as the value
// fits. They will also do text to any type, as long as
// the value parses as a value of the desired type. If
// you
// the value parses as a value of the desired type.
//
bool SetObject(UObject *Obj, WingOut Errors) const;
bool SetDouble(double D, WingOut Errors) const;
@@ -57,6 +57,10 @@ struct FWingProperty
//
FString GetTruncatedText(int32 MaxLen) const;
// Print the property's editflag, type, name, and value.
//
void Print(WingOut Out) const;
// Get the Category metadata.
//
FString GetCategory() const;
@@ -65,16 +69,22 @@ struct FWingProperty
//
explicit operator bool() const { return Prop != nullptr; }
// Get the raw properties of the specified object or struct.
// Get all the properties of the specified object or struct.
//
// This gets the properties that are literally present in the
// specified object or struct. No special interpretation is done.
//
static TArray<FWingProperty> GetAll(FWingStructAndUStruct Obj, EPropertyFlags Flags);
static TArray<FWingProperty> GetAll(FWingStructAndUStruct Obj);
// Get all the visible properties of the specified object or struct.
//
// This gets the properties that have CPF_Edit marked on them.
//
static TArray<FWingProperty> GetVisible(FWingStructAndUStruct Obj);
// Get just the names of the properties of the specified struct/class.
//
static TArray<FName> GetNames(UStruct *US, EPropertyFlags Flags);
static TArray<FName> GetVisibleNames(UStruct *US);
// Remove any properties with the specified name.
//
@@ -94,12 +104,10 @@ struct FWingProperty
// click a material graph node, the details panel shows you properties
// of the node, but also properties of the material expression.
//
// When editing an inherited ActorComponent, you're actually editing
// properties that *override* the original properties of the ActorComponent.
// The 'mutable' flag tells it whether to create overrides for these
// properties.
// If you do not specify mutable, then all properties will be marked
// non-editable.
//
static TArray<FWingProperty> GetDetails(UObject* Obj, EPropertyFlags Flags, bool Mutable);
static TArray<FWingProperty> GetDetails(UObject* Obj, bool Mutable);
// Functions to populate properties from a JSON object.
//
@@ -108,9 +116,26 @@ struct FWingProperty
static bool PopulateFromJson(TArray<FWingProperty>& Props, const FJsonValue& Json,
bool AllOptional, WingOut Errors);
// Functions to populate properties from a JSON object.
//
private:
static void GetAll(FWingStructAndUStruct Obj, EPropertyFlags Flags, TArray<FWingProperty> &Props);
static void StripEditable(TArray<FWingProperty> &Props);
static bool IsUnsigned(FNumericProperty* Prop);
static bool IsPinTypeProperty(FProperty *Prop);
void PrintExpectsReceived(const TCHAR *Type, WingOut Errors) const;
bool CheckImportTextResult(const FString &Value, WingOut Errors) const;
bool CheckEditable(WingOut Errors) const;
// Convert int64 to double losslessly. If it can't be done, generate an error.
static bool LosslessInt64ToDouble(int64 I, double &D, WingOut Errors);
// Convert double to int64 losslessly. If it can't be done, generate an error.
static bool LosslessDoubleToInt64(double D, int64 &I, WingOut Errors);
// Unlike SetDouble, this function requires that NProp is a floating point property.
static bool SetDoubleInternal(FNumericProperty *NProp, void *Container, double D, WingOut Errors);
// Unlike SetInt64, this function requires that NProp is an integral property.
static bool SetInt64Internal(FNumericProperty *NProp, void *Container, int64 I, WingOut Errors);
};

View File

@@ -19,7 +19,7 @@ class FSocket;
* UWingServer — editor subsystem that listens on a TCP socket and dispatches
* JSON commands to blueprint editing handlers.
*
* Clients connect via TCP and exchange newline-delimited JSON messages.
* Clients connect via TCP and exchange null-delimited JSON messages.
* Request format: {"command": "tool_name", "param1": "value1", ...}
* Response format: raw JSON result from the handler.
*
@@ -61,7 +61,7 @@ public:
// ----- Tool dispatch -----
static void AddHandler(UObject* Obj, const FString& Documentation);
static void AddHandler(UObject* Obj, const FString& Name, UObject* Config, EWingHandlerKind Kind, const FString& Documentation);
static void AddHandler(UObject* Obj, const FString& Name, UObject* Config, EWingHandlerKind Kind, UClass* FactoryClass, const FString& Documentation);
static const TArray<FWingHandlerConfig>& AllHandlers() { return GWingServer->WingHandlerRegistry; }
private:
@@ -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
@@ -96,6 +98,14 @@ private:
void AcceptNewConnections();
void CleanupFinishedClients();
static void ClientThreadFunc(UWingServer* Server, TSharedPtr<FClientConnection> Client);
static bool ExtractRequestFromBuffer(
TArray<uint8>& RecvBuf, int32& RecvLen, FString& OutRequest);
static bool ReceiveMoreBytesIntoBuffer(
FSocket* Socket, TArray<uint8>& RecvBuf, int32& RecvLen);
static bool SendAll(FSocket* Socket, const uint8* Data, int32 BytesToSend);
static bool ProcessRequestOnGameThread(
const FString& Request, FString& Response);
static void WaitForAssetRegistry();
// ----- Thread-safe message queue -----
struct FPendingMessage

View File

@@ -1,6 +1,6 @@
#pragma once
#include "CoreMinimal.h"
#include "WingHandler.h"
// -----------------------------------------------------------------
//
@@ -130,7 +130,7 @@ struct WingTokenizer
// Get the input text from a saved cursor position to the current
// cursor, optionally including additional tokens.
FStringView GetRange(FName SavePos, int32 Extra = 0) const;
FString GetRange(FName SavePos, int32 Extra = 0) const;
// Tokenize a line of input. The tokens are stored in
// the token array, and the cursor is positioned on the first

View File

@@ -29,8 +29,7 @@ public:
FName PinCategory;
FName PinSubCategory;
FString PinSubCategoryObject;
// The following are only set for interfaces and Objects.
const UClass *NativeParent = nullptr;
FName BroadCategory;
bool IsUserDefined = false;
};
@@ -68,6 +67,8 @@ private:
// Choose Short Name
// ---------------------------------------------------------------------------
static FName BroadCategory(const UClass* Class);
// Reserve the short name for a primitive type.
void ReserveShortName(FName Name, FName PinCategory, FName PinSubCategory);
void ReserveShortName(FName Name);
@@ -77,7 +78,7 @@ private:
// Core version: choose a short name for a path that doesn't already
// have a short name. Records all the associated information.
FString NewShortName(const FString &Path, FName PinCategory, const UClass *NativeParent, bool IsUserDefined);
FString NewShortName(const FString &Path, FName PinCategory, FName BroadCategory, bool IsUserDefined);
// Choose a short name for an already-loaded UObject.
FString ChooseShortName(const UObject* Obj);
@@ -136,9 +137,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 +155,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

View File

@@ -68,29 +68,43 @@ public:
static FName GetFName(const UWingComponentReference *Ref) { return Ref->VariableName; }
static FName GetFName(const UWidget *Widget) { return Widget->GetFName(); }
static FName GetFName(const WingVariables::Var &Var) { return Var.Name; }
static FName GetFName(const TSharedPtr<IPropertyHandle> &H);
////////////////////////////////////////////////////////
//
// When searching an array of structs, you want to
// return a pointer to one of the structs. When
// searching an array of pointers, you want to return
// one of the pointers. These helpers make it easy for
// the search templates below to do that.
// one of the pointers. When searching an array of
// smart pointers, you want to return one of the smart
// pointers. The AsPointerLike helper below makes it
// easy for the search templates below to do that.
//
////////////////////////////////////////////////////////
template<class T> static const T* EltAsPtr(const TArray<T> &A, const T &Elt) { return &Elt; }
template<class T> static T* EltAsPtr(TArray<T> &A, T &Elt) { return &Elt; }
template<class T> static T* EltAsPtr(const TArray<T*> &A, T* &Elt) { return Elt; }
template<class T> static T* EltAsPtr(TArray<T*> &A, T* &Elt) { return Elt; }
template<class T> static const T* EltAsPtr(const TArray<const T*> &A, const T* &Elt) { return Elt; }
template<class T> static const T* EltAsPtr(TArray<const T*> &A, const T* &Elt) { return Elt; }
template<class T> struct IsPointerLike : std::false_type {};
template<class T> struct IsPointerLike<T*> : std::true_type {};
template<class T> struct IsPointerLike<TSharedPtr<T>> : std::true_type {};
template<class T> struct IsPointerLike<TWeakObjectPtr<T>> : std::true_type {};
template<class T> struct IsPointerLike<TObjectPtr<T>> : std::true_type {};
template<class T> struct IsPointerLike<TStrongObjectPtr<T>> : std::true_type {};
template<class T>
static decltype(auto) AsPointerLike(T&& Elt)
{
if constexpr (IsPointerLike<std::remove_cvref_t<T>>::value)
{
return std::remove_cvref_t<T>(Elt);
}
else
{
return &Elt;
}
}
////////////////////////////////////////////////////////
//
// Search Templates. Search an array for an item with a
// given name. Works correctly no matter how a name is
// given name. Works correctly no matter how a name is
// expressed: eg, "A+B" finds the same results as
// "A&plus;B". Handles failure cases correctly: two
// items with the same name, no such item, and malformed
@@ -107,9 +121,9 @@ public:
template<typename ArrayType>
static auto FindOneWithInternalID(FName InternalID, ArrayType &&Array, const TCHAR *Kind, WingOut Errors)
{
decltype(EltAsPtr(Array, Array[0])) Result = nullptr;
decltype(AsPointerLike(Array[0])) Result = nullptr;
int Count = 0;
for (auto &Elt : Array) if (GetFName(Elt) == InternalID) { Count++; Result = EltAsPtr(Array, Elt); }
for (auto &Elt : Array) if (GetFName(Elt) == InternalID) { Count++; Result = AsPointerLike(Elt); }
if (!CheckExactlyOneNamed(Count, Kind, InternalID, Errors)) Result = nullptr;
return Result;
}
@@ -117,28 +131,12 @@ public:
template<typename ArrayType>
static auto FindOneWithExternalID(const FString &ExternalID, ArrayType &&Array, const TCHAR *Kind, WingOut Errors)
{
decltype(EltAsPtr(Array, Array[0])) Result = nullptr;
decltype(AsPointerLike(Array[0])) Result = nullptr;
FName InternalID = CheckInternalizeID(ExternalID, Errors);
if (!InternalID.IsNone()) Result = FindOneWithInternalID(InternalID, Array, Kind, Errors);
return Result;
}
template<typename ArrayType>
static bool FindNoneWithInternalID(FName InternalID, ArrayType &&Array, const TCHAR *Kind, WingOut Errors)
{
for (auto &Elt : Array) if (GetFName(Elt) == InternalID)
return CheckExactlyNoneNamed(1, Kind, InternalID, Errors);
return true;
}
template<typename ArrayType>
static bool FindNoneWithExternalID(const FString &ExternalID, ArrayType &&Array, const TCHAR *Kind, WingOut Errors)
{
FName InternalID = CheckInternalizeID(ExternalID, Errors);
if (InternalID.IsNone()) return false;
return FindNoneWithInternalID(InternalID, Array, Kind, Errors);
}
static bool FindNoDuplicateName(TSet<FName> &Collection, FName InternalID, const TCHAR *Kind, WingOut Errors)
{
if (Collection.Contains(InternalID))
@@ -276,9 +274,6 @@ public:
// ----- Common Error Reporting -----
static bool CheckExactlyOneNamed(int Count, const TCHAR *Kind, const FString &Name, WingOut Errors);
static bool CheckExactlyOneNamed(int Count, const TCHAR *Kind, FName Name, WingOut Errors);
static bool CheckExactlyNoneNamed(int Count, const TCHAR *Kind, const FString &Name, WingOut Errors);
static bool CheckExactlyNoneNamed(int Count, const TCHAR *Kind, FName Name, WingOut Errors);
static bool CheckCanRename(UEdGraphNode* Node, const FString &Name, WingOut Errors);
};

View File

@@ -68,7 +68,7 @@ def disconnect():
def send_and_receive(message):
"""Send a JSON message to the editor and return the null-terminated response."""
data = json.dumps(message) + "\n"
data = json.dumps(message) + "\0"
sock.sendall(data.encode())
result = b""

View File

@@ -41,7 +41,7 @@ def main():
print(f"Cannot connect to {HOST}:{PORT} — is the editor running?")
sys.exit(1)
sock.sendall((json.dumps(msg) + "\n").encode())
sock.sendall((json.dumps(msg) + "\0").encode())
result = b""
while True: