Lots of refactoring
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
#include "WingServer.h"
|
||||
#include "WingHandler.h"
|
||||
#include "WingFactories.h"
|
||||
#include "WingPropHandle.h"
|
||||
#include "WingProperty.h"
|
||||
#include "WingTypes.h"
|
||||
#include "Factories/BlueprintFactory.h"
|
||||
#include "Factories/BlueprintMacroFactory.h"
|
||||
@@ -80,18 +80,12 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the 'ParentClass' property.
|
||||
WingPropHandle Props;
|
||||
TSharedPtr<IPropertyHandle> PCProp = Props.NamedProperty(Factory, TEXT("ParentClass"), true, WingOut::Stdout);
|
||||
if (!PCProp) return;
|
||||
|
||||
// Store the parent class.
|
||||
FPropertyAccess::Result SetResult = PCProp->SetValue(ParentClassObj);
|
||||
if (SetResult != FPropertyAccess::Result::Success)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("ERROR: property does not allow value: %s\n"), *ParentClass);
|
||||
return;
|
||||
}
|
||||
// Set the 'ParentClass' property.
|
||||
TArray<FWingProperty> Props = FWingProperty::GetAll(Factory, CPF_Edit);
|
||||
FWingProperty *Prop = WingUtils::FindOneWithInternalID(
|
||||
TEXT("ParentClass"), Props, TEXT("property"), WingOut::Stdout);
|
||||
if (!Prop) return;
|
||||
if (!Prop->SetObject(ParentClassObj, WingOut::Stdout)) return;
|
||||
|
||||
// Create the asset using the factory.
|
||||
UObject *Blueprint = WingFactories::CreateAsset(Path, Factory, WingOut::Stdout);
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "WingServer.h"
|
||||
#include "WingHandler.h"
|
||||
#include "WingFactories.h"
|
||||
#include "WingPropHandle.h"
|
||||
#include "Factories/BlueprintFunctionLibraryFactory.h"
|
||||
#include "Create_UsingFactory.generated.h"
|
||||
|
||||
@@ -22,17 +21,10 @@ public:
|
||||
{
|
||||
TArray<UClass*> FactoryClasses;
|
||||
GetDerivedClasses(UFactory::StaticClass(), FactoryClasses);
|
||||
|
||||
for (UClass* Class : FactoryClasses)
|
||||
{
|
||||
if (Class->HasAnyClassFlags(CLASS_Abstract)) continue;
|
||||
|
||||
UFactory* CDO = Class->GetDefaultObject<UFactory>();
|
||||
if (!CDO->CanCreateNew() || !CDO->ShouldShowInNewMenu()) continue;
|
||||
|
||||
WingPropHandle Props;
|
||||
TArray<TSharedPtr<IPropertyHandle>> ConfigProps = Props.AllProperties(CDO, true);
|
||||
if (ConfigProps.Num() > 0) continue;
|
||||
if (!WingFactories::CanCreate(Class)) continue;
|
||||
if (WingFactories::GetParameterNames(Class).Num() > 0) continue;
|
||||
|
||||
FString FactoryName = WingFactories::DeriveFactoryName(Class);
|
||||
FString CommandName = FString::Printf(TEXT("Create_%s"), *FactoryName);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "WingFetcher.h"
|
||||
#include "WingProperty.h"
|
||||
#include "WingUtils.h"
|
||||
#include "WingTypes.h"
|
||||
#include "Details_Dump.generated.h"
|
||||
|
||||
UCLASS()
|
||||
@@ -45,14 +44,7 @@ public:
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("\n%s:\n"), *Pair.Key);
|
||||
for (const FWingProperty& P : Pair.Value)
|
||||
{
|
||||
bool bEditable = !P->HasAnyPropertyFlags(CPF_EditConst);
|
||||
WingOut::Stdout.Printf(TEXT(" %s %s %s = %s\n"),
|
||||
bEditable ? TEXT("editable") : TEXT("readonly"),
|
||||
*UWingTypes::TypeToText(P.Prop),
|
||||
*WingUtils::FormatName(P.Prop),
|
||||
*P.GetTruncatedText(100));
|
||||
}
|
||||
P.Print(WingOut::Stdout);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -28,6 +28,6 @@ public:
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
WingManual::Commands(EWingHandlerKind::Normal, Query, Verbose);
|
||||
WingManual::Commands(EWingHandlerKind::Create, Query, Verbose);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingHandler.h"
|
||||
#include "WingProperty.h"
|
||||
#include "WingFactories.h"
|
||||
#include "SysInfo_Factories.generated.h"
|
||||
|
||||
@@ -28,30 +27,45 @@ public:
|
||||
|
||||
virtual void Handle() override
|
||||
{
|
||||
// Build a set of factory classes that are already supported by Create commands.
|
||||
TSet<UClass*> SupportedFactories;
|
||||
for (const FWingHandlerConfig& H : UWingServer::AllHandlers())
|
||||
{
|
||||
if (H.Kind != EWingHandlerKind::Create) continue;
|
||||
UClass* FactoryClass = Cast<UClass>(H.Config.Get());
|
||||
if (FactoryClass && FactoryClass->IsChildOf(UFactory::StaticClass()))
|
||||
SupportedFactories.Add(FactoryClass);
|
||||
}
|
||||
|
||||
TArray<UClass*> FactoryClasses;
|
||||
GetDerivedClasses(UFactory::StaticClass(), FactoryClasses);
|
||||
|
||||
TArray<FString> Results;
|
||||
TArray<FString> Supported;
|
||||
TArray<FString> Unsupported;
|
||||
|
||||
for (UClass *Factory : FactoryClasses)
|
||||
{
|
||||
if (Factory->HasAnyClassFlags(CLASS_Abstract)) continue;
|
||||
UFactory *CDO = Factory->GetDefaultObject<UFactory>();
|
||||
if (!CDO->CanCreateNew()) continue;
|
||||
if (!CDO->ShouldShowInNewMenu()) continue;
|
||||
TArray<FName> Params = FWingProperty::GetNames(Factory, CPF_Edit);
|
||||
if (!WingFactories::CanCreate(Factory)) continue;
|
||||
TArray<FName> Params = WingFactories::GetParameterNames(Factory);
|
||||
TStringBuilder<512> Line;
|
||||
Line.Appendf(TEXT("%2d %s "), Params.Num(), *Factory->GetName());
|
||||
for (const FName& Prop : Params)
|
||||
{
|
||||
Line.Appendf(TEXT(" %s"), *Prop.ToString());
|
||||
if (SupportedFactories.Contains(Factory))
|
||||
Supported.Add(Line.ToString());
|
||||
else
|
||||
Unsupported.Add(Line.ToString());
|
||||
}
|
||||
Results.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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "WingServer.h"
|
||||
#include "WingUtils.h"
|
||||
#include "PackageTools.h"
|
||||
#include "WingProperty.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "Kismet2/EnumEditorUtils.h"
|
||||
|
||||
@@ -30,7 +31,7 @@ bool WingFactories::CheckNewAssetPath(const FString& Path, WingOut Errors)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WingFactories::IsBlacklisted(UClass* FactoryClass)
|
||||
bool WingFactories::IsBlacklisted(TSubclassOf<UFactory> FactoryClass)
|
||||
{
|
||||
FName Name = FactoryClass->GetFName();
|
||||
if (Name == TEXT("PhysicsAssetFactory")) return true; // Pops a modal dialog
|
||||
@@ -38,6 +39,20 @@ bool WingFactories::IsBlacklisted(UClass* FactoryClass)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WingFactories::CanCreate(TSubclassOf<UFactory> FactoryClass)
|
||||
{
|
||||
if (FactoryClass == nullptr) return false;
|
||||
if (FactoryClass->HasAnyClassFlags(CLASS_Abstract)) return false;
|
||||
if (IsBlacklisted(FactoryClass)) return false;
|
||||
UFactory* CDO = FactoryClass->GetDefaultObject<UFactory>();
|
||||
return CDO->CanCreateNew() && CDO->ShouldShowInNewMenu();
|
||||
}
|
||||
|
||||
TArray<FName> WingFactories::GetParameterNames(TSubclassOf<UFactory> FactoryClass)
|
||||
{
|
||||
return FWingProperty::GetNames(FactoryClass, CPF_Edit);
|
||||
}
|
||||
|
||||
UObject* WingFactories::CreateAsset(const FString& Path, UFactory* Factory, WingOut Errors)
|
||||
{
|
||||
// Check the blacklist.
|
||||
@@ -90,7 +105,7 @@ UObject* WingFactories::CreateAsset(const FString& Path, UFactory* Factory, Wing
|
||||
return NewAsset;
|
||||
}
|
||||
|
||||
FString WingFactories::DeriveFactoryName(UClass* FactoryClass)
|
||||
FString WingFactories::DeriveFactoryName(TSubclassOf<UFactory> FactoryClass)
|
||||
{
|
||||
FString Name = FactoryClass->GetName();
|
||||
int32 Index = Name.Find(TEXT("Factory"));
|
||||
|
||||
@@ -204,7 +204,7 @@ void WingGraphExport::EmitNode(UEdGraphNode* Node)
|
||||
Output.Appendf(TEXT("\nnode %s: %s\n"), *WingUtils::FormatName(Node), *WingUtils::FormatNodeTitle(Node));
|
||||
|
||||
// Emit node properties (if applicable).
|
||||
EmitNodeProperties(Node, Output, true);
|
||||
EmitNodeProperties(Node, &Output, true);
|
||||
|
||||
// Emit input data pins.
|
||||
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Input))
|
||||
@@ -248,26 +248,25 @@ void WingGraphExport::EmitNode(UEdGraphNode* Node)
|
||||
}
|
||||
}
|
||||
|
||||
void WingGraphExport::EmitNodeProperties(UEdGraphNode* Node, FStringBuilderBase& Out, bool bPrimary)
|
||||
void WingGraphExport::EmitNodeProperties(UEdGraphNode* Node, WingOut Out, bool bPrimary)
|
||||
{
|
||||
TArray<FWingProperty> Props = FWingProperty::GetDetails(Node, CPF_Edit, false);
|
||||
|
||||
FString PrimaryCategory;
|
||||
if (UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Node))
|
||||
{
|
||||
WingPropHandle::Handles Handles = Props.GetDetails(Node, false, WingOut::Stdout);
|
||||
PrimaryCategory = MatNode->MaterialExpression->GetClass()->GetName();
|
||||
for (const TSharedPtr<IPropertyHandle>& H : Handles)
|
||||
for (const FWingProperty& P : Props)
|
||||
{
|
||||
FString Category = H->GetProperty()->GetMetaData(TEXT("Category"));
|
||||
FString Category = P.GetCategory();
|
||||
if ((Category == PrimaryCategory) == bPrimary)
|
||||
WingPropHandle::Print(*H, Out);
|
||||
P.Print(Out);
|
||||
}
|
||||
}
|
||||
else if (bPrimary)
|
||||
{
|
||||
WingPropHandle::Handles Handles = Props.GetDetails(Node, false, WingOut::Stdout);
|
||||
for (const TSharedPtr<IPropertyHandle>& H : Handles)
|
||||
WingPropHandle::Print(*H, Out);
|
||||
for (const FWingProperty& P : Props)
|
||||
P.Print(Out);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +295,7 @@ void WingGraphExport::EmitDetails()
|
||||
Details.Appendf(TEXT("\ndetails %s\n"), *WingUtils::FormatName(Node));
|
||||
Details.Appendf(TEXT(" pos %d, %d\n"), Node->NodePosX, Node->NodePosY);
|
||||
|
||||
EmitNodeProperties(Node, Details, false);
|
||||
EmitNodeProperties(Node, &Details, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -338,6 +338,16 @@ FString FWingProperty::GetTruncatedText(int32 MaxLen) const
|
||||
}
|
||||
|
||||
|
||||
void FWingProperty::Print(WingOut Out) const
|
||||
{
|
||||
bool bEditable = !Prop->HasAnyPropertyFlags(CPF_EditConst);
|
||||
Out.Printf(TEXT(" %s %s %s = %s\n"),
|
||||
bEditable ? TEXT("editable") : TEXT("readonly"),
|
||||
*UWingTypes::TypeToText(Prop),
|
||||
*WingUtils::FormatName(Prop),
|
||||
*GetTruncatedText(100));
|
||||
}
|
||||
|
||||
FString FWingProperty::GetCategory() const
|
||||
{
|
||||
FString Result = Prop->GetMetaData(TEXT("Category"));
|
||||
|
||||
@@ -146,6 +146,7 @@ void UWingServer::Initialize(FSubsystemCollectionBase& Collection)
|
||||
}
|
||||
|
||||
BuildWingHandlerRegistry();
|
||||
ModulesChangedHandle = FModuleManager::Get().OnModulesChanged().AddUObject(this, &UWingServer::OnModulesChanged);
|
||||
LogCapture.bEnabled = false;
|
||||
LogCapture.Install();
|
||||
bRunning = true;
|
||||
@@ -154,6 +155,8 @@ void UWingServer::Initialize(FSubsystemCollectionBase& Collection)
|
||||
|
||||
void UWingServer::Deinitialize()
|
||||
{
|
||||
FModuleManager::Get().OnModulesChanged().Remove(ModulesChangedHandle);
|
||||
|
||||
if (!bRunning)
|
||||
{
|
||||
Super::Deinitialize();
|
||||
@@ -472,6 +475,7 @@ void UWingServer::AddHandler(UObject* Obj, const FString& Name, UObject* Config,
|
||||
|
||||
void UWingServer::BuildWingHandlerRegistry()
|
||||
{
|
||||
WingHandlerRegistry.Empty();
|
||||
for (UClass* Class : WingUtils::CollectHandlerClasses())
|
||||
{
|
||||
UWingHandler* CDO = Cast<UWingHandler>(Class->GetDefaultObject());
|
||||
@@ -480,6 +484,14 @@ void UWingServer::BuildWingHandlerRegistry()
|
||||
WingHandlerRegistry.Sort([](const FWingHandlerConfig& A, const FWingHandlerConfig& B) { return A.Name < B.Name; });
|
||||
}
|
||||
|
||||
void UWingServer::OnModulesChanged(FName ModuleName, EModuleChangeReason Reason)
|
||||
{
|
||||
if (Reason == EModuleChangeReason::ModuleLoaded)
|
||||
{
|
||||
BuildWingHandlerRegistry();
|
||||
}
|
||||
}
|
||||
|
||||
FWingHandlerConfig* UWingServer::FindHandler(const FString& Name)
|
||||
{
|
||||
int32 Index = Algo::LowerBoundBy(WingHandlerRegistry, Name, [](const FWingHandlerConfig& H) { return H.Name; });
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -57,6 +57,10 @@ struct FWingProperty
|
||||
//
|
||||
FString GetTruncatedText(int32 MaxLen) const;
|
||||
|
||||
// Print the property's type, name, and value.
|
||||
//
|
||||
void Print(WingOut Out) const;
|
||||
|
||||
// Get the Category metadata.
|
||||
//
|
||||
FString GetCategory() const;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user