Refactoring ue-wingman to be a command-line only tool
This commit is contained in:
@@ -25,7 +25,7 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Asset to delete"))
|
||||
FString Asset;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="If true, skip reference check and force delete"))
|
||||
UPROPERTY(EditAnywhere, meta=(Description="If true, skip reference check and force delete"))
|
||||
bool Force = false;
|
||||
|
||||
virtual void Register() override
|
||||
|
||||
@@ -21,13 +21,13 @@ class UWing_Asset_Search : public UWingHandler
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Substring to match against asset package paths"))
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Substring to match against asset package paths"))
|
||||
FString Query;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Asset class name to filter by, e.g. Blueprint, Material, StaticMesh"))
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Asset class name to filter by, e.g. Blueprint, Material, StaticMesh"))
|
||||
FString Type;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Maximum number of results (default 50)"))
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Maximum number of results (default 50)"))
|
||||
int32 Limit = 50;
|
||||
|
||||
virtual void Register() override
|
||||
|
||||
@@ -32,16 +32,15 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Type of graph: function or macro"))
|
||||
FString GraphType;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Input variables, one per line"))
|
||||
FString InputVariables;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Output variables, one per line"))
|
||||
FString OutputVariables;
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Variables"))
|
||||
FWingRestOfArgv Variables;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Create a new function or macro graph in a Blueprint."));
|
||||
TEXT("Create a new function or macro graph in a Blueprint. "
|
||||
"Variables must be expressed as 'kind type name (flags) = default'. "
|
||||
"Kind can be input, output, or local."));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
@@ -79,8 +78,7 @@ public:
|
||||
|
||||
// Parse and validate variables before making changes
|
||||
WingVariables Vars;
|
||||
if (!Vars.InputVariables.ParseString(InputVariables, WingOut::Stdout)) return;
|
||||
if (!Vars.OutputVariables.ParseString(OutputVariables, WingOut::Stdout)) return;
|
||||
if (!Vars.Parse(Variables.Argv, false, WingOut::Stdout)) return;
|
||||
|
||||
// Create the Graph
|
||||
UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(BP, InternalID,
|
||||
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Interface name to remove"))
|
||||
FString Interface;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="If true, keep the function graphs as regular functions"))
|
||||
UPROPERTY(EditAnywhere, meta=(Description="If true, keep the function graphs as regular functions"))
|
||||
bool PreserveFunctions = false;
|
||||
|
||||
virtual void Register() override
|
||||
|
||||
@@ -37,5 +37,6 @@ public:
|
||||
if (!P) return;
|
||||
|
||||
WingOut::Stdout.Print(P->GetText());
|
||||
WingOut::Stdout.Print(TEXT("\n"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingBasics.h"
|
||||
#include "WingFetcher.h"
|
||||
#include "WingProperty.h"
|
||||
#include "WingUtils.h"
|
||||
#include "Details_SetMany.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class UWing_Details_SetMany : public UWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Target object"))
|
||||
FString Object;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Object mapping property names to new values in Unreal text format"))
|
||||
FWingJsonObject Properties;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Set one or more editable properties. Values use Unreal text format."));
|
||||
}
|
||||
|
||||
virtual void Handle() override
|
||||
{
|
||||
WingFetcher F(WingOut::Stdout);
|
||||
UObject* Obj = F.Walk(Object).Cast<UObject>();
|
||||
if (!Obj) return;
|
||||
|
||||
if (!Properties.Json || Properties.Json->Values.Num() == 0)
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT("Error: No properties specified\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, true);
|
||||
|
||||
// Validation pass — resolve all properties before modifying anything.
|
||||
for (const auto& Pair : Properties.Json->Values)
|
||||
{
|
||||
FWingProperty* P = WingUtils::FindOneWithExternalID(Pair.Key, Props, TEXT("Property"), WingOut::Stdout);
|
||||
if (!P) return;
|
||||
}
|
||||
|
||||
// Assignment pass — store the values.
|
||||
int SuccessCount = 0;
|
||||
for (const auto& Pair : Properties.Json->Values)
|
||||
{
|
||||
FWingProperty* P = WingUtils::FindOneWithExternalID(Pair.Key, Props, TEXT("Property"), WingOut::Stdout);
|
||||
if (P->SetJson(*Pair.Value, WingOut::Stdout)) SuccessCount++;
|
||||
}
|
||||
|
||||
WingOut::Stdout.Printf(TEXT("Set %d/%d properties.\n"), SuccessCount, Properties.Json->Values.Num());
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingBasics.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingManual.h"
|
||||
#include "Documentation_Command.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class UWing_Documentation_Command : public UWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Substring filter for command names"))
|
||||
FString Command;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Detailed documentation for one or more commands."));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
WingManual::Commands(EWingHandlerKind::Normal, Command, true);
|
||||
}
|
||||
};
|
||||
@@ -12,19 +12,13 @@ class UWing_Documentation_Commands : public UWingHandler
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Substring filter for command names"))
|
||||
FString Query;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="If true, return full details including parameter types and descriptions"))
|
||||
bool Verbose = false;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("List all the main commands with their descriptions."));
|
||||
TEXT("A concise list of all ue-wingman commands."));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
WingManual::Commands(EWingHandlerKind::Normal, Query, Verbose);
|
||||
WingManual::Commands(EWingHandlerKind::Normal, TEXT(""), false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,10 +12,10 @@ class UWing_Documentation_CreateAssets : public UWingHandler
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Substring filter for command names"))
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Substring filter for command names"))
|
||||
FString Query;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="If true, return full details including parameter types and descriptions"))
|
||||
UPROPERTY(EditAnywhere, meta=(Description="If true, return full details including parameter types and descriptions"))
|
||||
bool Verbose = false;
|
||||
|
||||
virtual void Register() override
|
||||
|
||||
@@ -12,7 +12,7 @@ class UWing_Documentation_Manual : public UWingHandler
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="section of the manual"))
|
||||
UPROPERTY(EditAnywhere, meta=(Description="section of the manual"))
|
||||
FString Section;
|
||||
|
||||
virtual void Register() override
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
}
|
||||
else
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("Unknown manual section '%s'\n"));
|
||||
WingOut::Stdout.Printf(TEXT("Unknown manual section '%s'\n"), *Section);
|
||||
WingManual::PrintSectionNames(TEXT("Valid manual sections:"), Sections, WingOut::Stdout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,13 +28,16 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Name of the new event dispatcher"))
|
||||
FString Dispatcher;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Input Variables, one per line, expressed as: type var = value"))
|
||||
FString InputVariables;
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Variables"))
|
||||
FWingRestOfArgv Variables;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Add a new event dispatcher to a Blueprint."));
|
||||
TEXT("Add a new event dispatcher to a Blueprint. "
|
||||
"Variables must be expressed as 'kind type name (flags) = default'. "
|
||||
"Kind can only be 'input'."));
|
||||
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
@@ -49,7 +52,7 @@ public:
|
||||
|
||||
// Parse the arguments.
|
||||
WingVariables Vars;
|
||||
if (!Vars.InputVariables.ParseString(InputVariables, WingOut::Stdout)) return;
|
||||
if (!Vars.Parse(Variables.Argv, false, WingOut::Stdout)) return;
|
||||
|
||||
// Add the delegate variable
|
||||
FEdGraphPinType DelegateType;
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingBasics.h"
|
||||
#include "WingFetcher.h"
|
||||
#include "WingProperty.h"
|
||||
#include "WingUtils.h"
|
||||
#include "WingGraphActions.h"
|
||||
#include "WingGraphExport.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "GraphNode_Add.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
USTRUCT()
|
||||
struct FSpawnNodeEntry
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY()
|
||||
FString Type;
|
||||
|
||||
UPROPERTY()
|
||||
int32 PosX = 0;
|
||||
|
||||
UPROPERTY()
|
||||
int32 PosY = 0;
|
||||
|
||||
FWingGraphAction *Action;
|
||||
};
|
||||
|
||||
|
||||
UCLASS()
|
||||
class UWing_GraphNode_Add : public UWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Target graph"))
|
||||
FString Graph;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Array of {Type, posX, posY} objects. Use GraphNode_SearchTypes to find types."))
|
||||
FWingJsonArray Nodes;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Create nodes using the editor's action database. "
|
||||
"Use GraphNode_SearchTypes to find types."));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
WingFetcher F(WingOut::Stdout);
|
||||
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
|
||||
if (!TargetGraph) return;
|
||||
|
||||
int32 SuccessCount = 0;
|
||||
int32 TotalCount = Nodes.Array.Num();
|
||||
FWingGraphActions GraphActions(TargetGraph);
|
||||
|
||||
// Parse the json array, turning it into an array of spawn node entries.
|
||||
TArray<FSpawnNodeEntry> Entries;
|
||||
FSpawnNodeEntry Entry;
|
||||
TArray<FWingProperty> Props = FWingProperty::GetAll(nullptr, &Entry, FSpawnNodeEntry::StaticStruct(), true);
|
||||
for (const TSharedPtr<FJsonValue>& Elt : Nodes.Array)
|
||||
{
|
||||
if (!FWingProperty::PopulateFromJson(Props, *Elt, false, WingOut::Stdout)) return;
|
||||
TArray<FWingGraphAction*> Results = GraphActions.Search(Entry.Type, 2, true);
|
||||
if (!WingUtils::CheckExactlyOneNamed(Results.Num(), TEXT("node type"), Entry.Type, WingOut::Stdout)) return;
|
||||
Entry.Action = Results[0];
|
||||
Entries.Add(Entry);
|
||||
}
|
||||
|
||||
// Execute all.
|
||||
for (const FSpawnNodeEntry &SpawnEntry : Entries)
|
||||
{
|
||||
UEdGraphNode* NewNode = SpawnEntry.Action->Execute(FVector2D(SpawnEntry.PosX, SpawnEntry.PosY));
|
||||
if (NewNode)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("Spawned: %s\n"), *SpawnEntry.Type);
|
||||
WingGraphExport Export(NewNode, false, true);
|
||||
WingOut::Stdout.Print(Export.GetOutput());
|
||||
}
|
||||
else
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("Failed: %s\n\n"), *SpawnEntry.Type);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Target node"))
|
||||
FString Node;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="True to show minor node properties"))
|
||||
UPROPERTY(EditAnywhere, meta=(Description="True to show minor node properties"))
|
||||
bool Details = false;
|
||||
|
||||
virtual void Register() override
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingBasics.h"
|
||||
#include "WingFetcher.h"
|
||||
#include "WingGraphActions.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "GraphNode_SearchTypes.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
UCLASS()
|
||||
class UWing_GraphNode_SearchTypes : public UWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Array of query strings; each may contain * wildcards"))
|
||||
FWingJsonArray Queries;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Maximum number of results per query (default 50)"))
|
||||
int32 MaxResults = 50;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Target graph"))
|
||||
FString Graph;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Search for node types that can be spawned in a graph. "
|
||||
"Pass a string returned by this function to GraphNode_Add."));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
WingFetcher F(WingOut::Stdout);
|
||||
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
|
||||
if (!TargetGraph) return;
|
||||
|
||||
// Validate all entries are strings before running any searches.
|
||||
TArray<FString> QueryStrings;
|
||||
QueryStrings.Reserve(Queries.Array.Num());
|
||||
for (const TSharedPtr<FJsonValue>& QueryVal : Queries.Array)
|
||||
{
|
||||
FString QueryStr;
|
||||
if (!QueryVal->TryGetString(QueryStr))
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT("ERROR: Queries must be an array of strings.\n"));
|
||||
return;
|
||||
}
|
||||
QueryStrings.Add(QueryStr);
|
||||
}
|
||||
|
||||
FWingGraphActions GraphActions(TargetGraph);
|
||||
for (const FString& Query : QueryStrings)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("\n=== %s ===\n\n"), *Query);
|
||||
TArray<FWingGraphAction*> Results = GraphActions.Search(Query, MaxResults, false);
|
||||
for (const FWingGraphAction* Action : Results)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("%s\n"), *Action->Name);
|
||||
}
|
||||
|
||||
if (Results.Num() == 0)
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT("No matching node types found.\n"));
|
||||
}
|
||||
else if (Results.Num() >= MaxResults)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("WARNING: Reached limit of %d results. You may specify MaxResults.\n"), MaxResults);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,136 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingBasics.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingFetcher.h"
|
||||
#include "WingProperty.h"
|
||||
#include "WingUtils.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "EdGraphSchema_K2.h"
|
||||
#include "MaterialGraph/MaterialGraphSchema.h"
|
||||
#include "GraphNode_SetDefaults.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
USTRUCT()
|
||||
struct FSetNodeDefaultEntry
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY()
|
||||
FString Node;
|
||||
|
||||
UPROPERTY()
|
||||
FString Name;
|
||||
|
||||
UPROPERTY()
|
||||
FString Value;
|
||||
};
|
||||
|
||||
|
||||
UCLASS()
|
||||
class UWing_GraphNode_SetDefaults : public UWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Target graph"))
|
||||
FString Graph;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Array of {node, name, value} objects"))
|
||||
FWingJsonArray Pins;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Set the default value of input pins or material expression properties on nodes."));
|
||||
}
|
||||
// -----------------------------------------------------------------------
|
||||
// K2 graphs: set pin default values.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void HandleK2Entry(const FSetNodeDefaultEntry& Entry, UEdGraph* GraphObj, const UEdGraphSchema_K2* K2Schema)
|
||||
{
|
||||
WingFetcher F(GraphObj, WingOut::Stdout);
|
||||
UWingGraphPinRef* PinRef = F.Node(Entry.Node).Pin(Entry.Name).Cast<UWingGraphPinRef>();
|
||||
if (!PinRef) return;
|
||||
UEdGraphPin* Pin = WingUtils::CheckGetPin(PinRef->Node, PinRef->PinName, WingOut::Stdout);
|
||||
if (!Pin) return;
|
||||
|
||||
UEdGraphNode* Node = Pin->GetOwningNode();
|
||||
|
||||
if (Pin->Direction != EGPD_Input)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("error: %s is an output pin\n"), *WingUtils::FormatName(Pin));
|
||||
return;
|
||||
}
|
||||
|
||||
FString UseDefaultValue;
|
||||
TObjectPtr<UObject> UseDefaultObject = nullptr;
|
||||
FText UseDefaultText;
|
||||
K2Schema->GetPinDefaultValuesFromString(Pin->PinType, Node, Entry.Value, UseDefaultValue, UseDefaultObject, UseDefaultText, false);
|
||||
FString Error = K2Schema->IsPinDefaultValid(Pin, UseDefaultValue, UseDefaultObject, UseDefaultText);
|
||||
if (!Error.IsEmpty())
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("error: %s: %s\n"), *WingUtils::FormatName(Pin), *Error);
|
||||
return;
|
||||
}
|
||||
UWingServer::AddTouchedObject(Node);
|
||||
K2Schema->TrySetDefaultValue(*Pin, Entry.Value);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Material graphs: set material expression properties.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void HandleMaterialEntry(const FSetNodeDefaultEntry& Entry, UEdGraph* GraphObj)
|
||||
{
|
||||
WingFetcher F(GraphObj, WingOut::Stdout);
|
||||
UEdGraphNode* Node = F.Node(Entry.Node).Cast<UEdGraphNode>();
|
||||
if (!Node) return;
|
||||
|
||||
TArray<FWingProperty> All = FWingProperty::GetDetails(Node, true);
|
||||
FWingProperty *P = WingUtils::FindOneWithExternalID(Entry.Name, All, TEXT("Property"), WingOut::Stdout);
|
||||
if (!P) return;
|
||||
|
||||
UWingServer::AddTouchedObject(Node);
|
||||
|
||||
if (!P->SetText(Entry.Value, WingOut::Stdout))
|
||||
return;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
virtual void Handle() override
|
||||
{
|
||||
// Fetch the graph once.
|
||||
WingFetcher GraphFetcher(WingOut::Stdout);
|
||||
UEdGraph* GraphObj = GraphFetcher.Walk(Graph).Cast<UEdGraph>();
|
||||
if (!GraphObj) return;
|
||||
|
||||
const UEdGraphSchema* Schema = GraphObj->GetSchema();
|
||||
const UEdGraphSchema_K2* K2Schema = Cast<UEdGraphSchema_K2>(Schema);
|
||||
const UMaterialGraphSchema* MGSchema = Cast<UMaterialGraphSchema>(Schema);
|
||||
|
||||
if (!K2Schema && !MGSchema)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("error: unsupported graph schema %s\n"), *Schema->GetClass()->GetName());
|
||||
return;
|
||||
}
|
||||
|
||||
FSetNodeDefaultEntry Entry;
|
||||
TArray<FWingProperty> Props = FWingProperty::GetAll(nullptr, &Entry, FSetNodeDefaultEntry::StaticStruct(), true);
|
||||
for (const TSharedPtr<FJsonValue>& PinVal : Pins.Array)
|
||||
{
|
||||
if (!FWingProperty::PopulateFromJson(Props, *PinVal, false, WingOut::Stdout)) continue;
|
||||
if (K2Schema) HandleK2Entry(Entry, GraphObj, K2Schema);
|
||||
else if (MGSchema) HandleMaterialEntry(Entry, GraphObj);
|
||||
}
|
||||
|
||||
WingOut::Stdout.Printf(TEXT("Done.\n"));
|
||||
}
|
||||
};
|
||||
@@ -1,73 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingBasics.h"
|
||||
#include "WingFetcher.h"
|
||||
#include "WingProperty.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "GraphNode_SetPositions.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
USTRUCT()
|
||||
struct FMoveNodeEntry
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY()
|
||||
FString Node;
|
||||
|
||||
UPROPERTY()
|
||||
int32 X = 0;
|
||||
|
||||
UPROPERTY()
|
||||
int32 Y = 0;
|
||||
};
|
||||
|
||||
|
||||
UCLASS()
|
||||
class UWing_GraphNode_SetPositions : public UWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Target graph"))
|
||||
FString Graph;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Array of {node, x, y} objects"))
|
||||
FWingJsonArray Nodes;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Reposition one or more nodes in a Blueprint graph."));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
WingFetcher F(WingOut::Stdout);
|
||||
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
|
||||
if (!TargetGraph) return;
|
||||
|
||||
int32 SuccessCount = 0;
|
||||
|
||||
FMoveNodeEntry Entry;
|
||||
TArray<FWingProperty> Props = FWingProperty::GetAll(nullptr, &Entry, FMoveNodeEntry::StaticStruct(), true);
|
||||
for (const TSharedPtr<FJsonValue>& Elt : Nodes.Array)
|
||||
{
|
||||
if (!FWingProperty::PopulateFromJson(Props, *Elt, false, WingOut::Stdout)) continue;
|
||||
WingFetcher FN(TargetGraph, WingOut::Stdout);
|
||||
UEdGraphNode* Node = FN.Node(Entry.Node).Cast<UEdGraphNode>();
|
||||
if (!Node) continue;
|
||||
Node->NodePosX = Entry.X;
|
||||
Node->NodePosY = Entry.Y;
|
||||
SuccessCount++;
|
||||
}
|
||||
|
||||
WingOut::Stdout.Printf(TEXT("Moved %d/%d nodes.\n"), SuccessCount, Nodes.Array.Num());
|
||||
}
|
||||
};
|
||||
@@ -1,96 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingBasics.h"
|
||||
#include "WingFetcher.h"
|
||||
#include "WingProperty.h"
|
||||
#include "WingUtils.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphSchema.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "GraphPin_Connect.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
USTRUCT()
|
||||
struct FConnectPinsEntry
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY()
|
||||
FString SourcePin;
|
||||
|
||||
UPROPERTY()
|
||||
FString TargetPin;
|
||||
};
|
||||
|
||||
|
||||
UCLASS()
|
||||
class UWing_GraphPin_Connect : public UWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Target graph"))
|
||||
FString Graph;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Array of {sourcePin, targetPin} objects"))
|
||||
FWingJsonArray Connections;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Connect pins between nodes in a graph (Blueprint or Material). "
|
||||
"Pin IDs use fetcher path syntax relative to the graph, eg: "
|
||||
"node:K2Node_CallFunction_0,pin:ReturnValue"));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
WingFetcher F(WingOut::Stdout);
|
||||
UEdGraph* G = F.Walk(Graph).Cast<UEdGraph>();
|
||||
if (!G) return;
|
||||
|
||||
int32 SuccessCount = 0;
|
||||
int32 TotalCount = Connections.Array.Num();
|
||||
|
||||
FConnectPinsEntry Entry;
|
||||
TArray<FWingProperty> EntryProps = FWingProperty::GetAll(nullptr, &Entry, FConnectPinsEntry::StaticStruct(), true);
|
||||
for (const TSharedPtr<FJsonValue>& ConnVal : Connections.Array)
|
||||
{
|
||||
if (!FWingProperty::PopulateFromJson(EntryProps, *ConnVal, false, WingOut::Stdout))
|
||||
continue;
|
||||
|
||||
WingFetcher FS(G, WingOut::Stdout);
|
||||
UWingGraphPinRef* SourcePinRef = FS.Walk(Entry.SourcePin).Cast<UWingGraphPinRef>();
|
||||
if (!SourcePinRef) continue;
|
||||
UEdGraphPin* SourcePin = WingUtils::CheckGetPin(SourcePinRef->Node, SourcePinRef->PinName, WingOut::Stdout);
|
||||
if (!SourcePin) continue;
|
||||
|
||||
WingFetcher FT(G, WingOut::Stdout);
|
||||
UWingGraphPinRef* TargetPinRef = FT.Walk(Entry.TargetPin).Cast<UWingGraphPinRef>();
|
||||
if (!TargetPinRef) continue;
|
||||
UEdGraphPin* TargetPin = WingUtils::CheckGetPin(TargetPinRef->Node, TargetPinRef->PinName, WingOut::Stdout);
|
||||
if (!TargetPin) continue;
|
||||
|
||||
const UEdGraphSchema* Schema = G->GetSchema();
|
||||
const FPinConnectionResponse Response = Schema->CanCreateConnection(SourcePin, TargetPin);
|
||||
if (Response.Response == CONNECT_RESPONSE_DISALLOW)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("error: Cannot connect %s.%s to %s.%s: %s\n"),
|
||||
*WingUtils::FormatName(SourcePin->GetOwningNode()), *WingUtils::FormatName(SourcePin),
|
||||
*WingUtils::FormatName(TargetPin->GetOwningNode()), *WingUtils::FormatName(TargetPin),
|
||||
*Response.Message.ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
Schema->TryCreateConnection(SourcePin, TargetPin);
|
||||
SuccessCount++;
|
||||
}
|
||||
|
||||
WingOut::Stdout.Printf(TEXT("Connected %d/%d pins.\n"), SuccessCount, TotalCount);
|
||||
}
|
||||
};
|
||||
@@ -1,77 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingBasics.h"
|
||||
#include "WingFetcher.h"
|
||||
#include "WingProperty.h"
|
||||
#include "WingUtils.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "GraphPin_Disconnect.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
UCLASS()
|
||||
class UWing_GraphPin_Disconnect : public UWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Target graph"))
|
||||
FString Graph;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Array of pin ID strings"))
|
||||
FWingJsonArray Pins;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Disconnect all connections on the specified pins. "
|
||||
"Pin IDs use fetcher path syntax relative to the graph, eg: "
|
||||
"node:K2Node_CallFunction_0,pin:ReturnValue"));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
WingFetcher F(WingOut::Stdout);
|
||||
UEdGraph* G = F.Walk(Graph).Cast<UEdGraph>();
|
||||
if (!G) return;
|
||||
|
||||
int32 SuccessCount = 0;
|
||||
int32 TotalDisconnected = 0;
|
||||
|
||||
for (const TSharedPtr<FJsonValue>& PinVal : Pins.Array)
|
||||
{
|
||||
FString PinPath;
|
||||
if (!PinVal->TryGetString(PinPath))
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT("ERROR: Expected a string pin ID.\n"));
|
||||
continue;
|
||||
}
|
||||
|
||||
WingFetcher FP(G, WingOut::Stdout);
|
||||
UWingGraphPinRef* PinRef = FP.Walk(PinPath).Cast<UWingGraphPinRef>();
|
||||
if (!PinRef) continue;
|
||||
UEdGraphPin* Pin = WingUtils::CheckGetPin(PinRef->Node, PinRef->PinName, WingOut::Stdout);
|
||||
if (!Pin) continue;
|
||||
|
||||
int32 DisconnectedCount = Pin->LinkedTo.Num();
|
||||
if (DisconnectedCount > 0)
|
||||
{
|
||||
Pin->BreakAllPinLinks(true);
|
||||
}
|
||||
|
||||
WingOut::Stdout.Printf(TEXT("Disconnected %d link(s) from %s.%s\n"),
|
||||
DisconnectedCount,
|
||||
*WingUtils::FormatName(Pin->GetOwningNode()), *WingUtils::FormatName(Pin));
|
||||
SuccessCount++;
|
||||
TotalDisconnected += DisconnectedCount;
|
||||
}
|
||||
|
||||
WingOut::Stdout.Printf(TEXT("Done: %d/%d succeeded, %d links broken.\n"),
|
||||
SuccessCount, Pins.Array.Num(), TotalDisconnected);
|
||||
}
|
||||
};
|
||||
@@ -22,7 +22,7 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Path to graph"))
|
||||
FString Graph;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="True to show minor node properties"))
|
||||
UPROPERTY(EditAnywhere, meta=(Description="True to show minor node properties"))
|
||||
bool Details = false;
|
||||
|
||||
virtual void Register() override
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingBasics.h"
|
||||
#include "Sequence.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
UCLASS()
|
||||
class UWing_Sequence : public UWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description=
|
||||
"Array of subcommand JSON objects to execute in order. Each must contain 'command' and its parameters."))
|
||||
FWingJsonArray Subcommands;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Execute multiple commands in one request. Each subcommand "
|
||||
"produces its own content block in the response. The big win "
|
||||
"performance-wise is that fewer MCP calls means fewer "
|
||||
"round-trip invocations of the LLM."));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
// The actual code that implements Sequence is hardwired into
|
||||
// WingServer. Because of that, this handler is never actually called
|
||||
// under normal conditions. The handler exists for two reasons: to
|
||||
// provide documentation, and also to catch the case where somebody
|
||||
// nests a sequence inside another sequence.
|
||||
WingOut::Stdout.Print(
|
||||
TEXT("ERROR: Sequence inside a Sequence is not allowed.\n"));
|
||||
}
|
||||
};
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Substring filter for type names"))
|
||||
FString Query;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Maximum number of results"))
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Maximum number of results"))
|
||||
int32 Limit = 100;
|
||||
|
||||
virtual void Register() override
|
||||
|
||||
@@ -21,22 +21,16 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Path to a blueprint, graph, or custom event node"))
|
||||
FString Object;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Blueprint variables, one per line"))
|
||||
FString BlueprintVariables;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Input variables, one per line"))
|
||||
FString InputVariables;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Output variables, one per line"))
|
||||
FString OutputVariables;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Local variables, one per line"))
|
||||
FString LocalVariables;
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Variable descriptions"))
|
||||
FWingRestOfArgv Variables;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Add new variables. Format: 'type name (flags) = default', one per line."));
|
||||
TEXT("Add variables to a blueprint, function graph, "
|
||||
"macro graph, event dispatcher graph, or custom event node. "
|
||||
"Each variable must be expressed as: 'kind type name (flags) = default'. "
|
||||
"Kind can be blueprint, input, output, or local."));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
@@ -46,10 +40,7 @@ public:
|
||||
|
||||
WingVariables Vars;
|
||||
if (!Vars.SetBackingStore(Obj, WingOut::Stdout)) return;
|
||||
if (!Vars.BlueprintVariables.ParseString(BlueprintVariables, WingOut::Stdout)) return;
|
||||
if (!Vars.InputVariables.ParseString(InputVariables, WingOut::Stdout)) return;
|
||||
if (!Vars.OutputVariables.ParseString(OutputVariables, WingOut::Stdout)) return;
|
||||
if (!Vars.LocalVariables.ParseString(LocalVariables, WingOut::Stdout)) return;
|
||||
if (!Vars.Parse(Variables.Argv, false, WingOut::Stdout)) return;
|
||||
if (!Vars.Check(WingOut::Stdout)) return;
|
||||
if (!Vars.Create(WingOut::Stdout)) return;
|
||||
WingOut::Stdout.Printf(TEXT("Success.\n"));
|
||||
|
||||
@@ -21,23 +21,16 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Path to a blueprint, graph, or custom event node"))
|
||||
FString Object;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Blueprint variables, one per line"))
|
||||
FString BlueprintVariables;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Input variables, one per line"))
|
||||
FString InputVariables;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Output variables, one per line"))
|
||||
FString OutputVariables;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Local variables, one per line"))
|
||||
FString LocalVariables;
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Variable descriptions"))
|
||||
FWingRestOfArgv Variables;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Modify variables of a blueprint, function graph, "
|
||||
"macro graph, event dispatcher graph, or custom event node. "));
|
||||
TEXT("Add variables to a blueprint, function graph, "
|
||||
"macro graph, event dispatcher graph, or custom event node. "
|
||||
"Each variable must be expressed as: 'kind type name (flags) = default'. "
|
||||
"Kind can be blueprint, input, output, or local."));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
@@ -47,10 +40,7 @@ public:
|
||||
|
||||
WingVariables Vars;
|
||||
if (!Vars.SetBackingStore(Obj, WingOut::Stdout)) return;
|
||||
if (!Vars.BlueprintVariables.ParseString(BlueprintVariables, WingOut::Stdout)) return;
|
||||
if (!Vars.InputVariables.ParseString(InputVariables, WingOut::Stdout)) return;
|
||||
if (!Vars.OutputVariables.ParseString(OutputVariables, WingOut::Stdout)) return;
|
||||
if (!Vars.LocalVariables.ParseString(LocalVariables, WingOut::Stdout)) return;
|
||||
if (!Vars.Parse(Variables.Argv, false, WingOut::Stdout)) return;
|
||||
if (!Vars.Check(WingOut::Stdout)) return;
|
||||
if (!Vars.Modify(WingOut::Stdout)) return;
|
||||
WingOut::Stdout.Printf(TEXT("Success.\n"));
|
||||
|
||||
@@ -21,22 +21,15 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Path to a blueprint, graph, or custom event node"))
|
||||
FString Object;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Blueprint variable names to remove, comma-separated"))
|
||||
FString BlueprintVariables;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Input variable names to remove, comma-separated"))
|
||||
FString InputVariables;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Output variable names to remove, comma-separated"))
|
||||
FString OutputVariables;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Local variable names to remove, comma-separated"))
|
||||
FString LocalVariables;
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Variable descriptions"))
|
||||
FWingRestOfArgv Variables;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Remove variables from a blueprint, graph, or custom event node."));
|
||||
TEXT("Remove variables from a blueprint, graph, or custom event node. "
|
||||
"Each variable must be expressed as: 'kind name'. "
|
||||
"Kind can be blueprint, input, output, or local."));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
@@ -46,10 +39,7 @@ public:
|
||||
|
||||
WingVariables Vars;
|
||||
if (!Vars.SetBackingStore(Obj, WingOut::Stdout)) return;
|
||||
if (!Vars.BlueprintVariables.ParseNamesString(BlueprintVariables, WingOut::Stdout)) return;
|
||||
if (!Vars.InputVariables.ParseNamesString(InputVariables, WingOut::Stdout)) return;
|
||||
if (!Vars.OutputVariables.ParseNamesString(OutputVariables, WingOut::Stdout)) return;
|
||||
if (!Vars.LocalVariables.ParseNamesString(LocalVariables, WingOut::Stdout)) return;
|
||||
if (!Vars.Parse(Variables.Argv, true, WingOut::Stdout)) return;
|
||||
if (!Vars.Remove(WingOut::Stdout)) return;
|
||||
WingOut::Stdout.Printf(TEXT("Success.\n"));
|
||||
}
|
||||
|
||||
@@ -33,10 +33,10 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Name for the new widget"))
|
||||
FString Name;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Parent widget name. If omitted, sets as root."))
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Parent widget name. If omitted, sets as root."))
|
||||
FString Parent;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Whether to expose the widget as a variable in the blueprint (default false)"))
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Whether to expose the widget as a variable in the blueprint (default false)"))
|
||||
bool IsVariable = false;
|
||||
|
||||
virtual void Register() override
|
||||
@@ -117,6 +117,10 @@ public:
|
||||
BP->WidgetTree->RootWidget = NewWidget;
|
||||
}
|
||||
|
||||
// Register a variable GUID for the new widget. UMG's compiler
|
||||
// ensures every widget in the tree is present in this map.
|
||||
BP->OnVariableAdded(NewWidget->GetFName());
|
||||
|
||||
WingOut::Stdout.Printf(TEXT("Created widget '%s' of type '%s'\n"), *Name, *Type);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingBasics.h"
|
||||
#include "WingWidgets.h"
|
||||
#include "Widget_SearchTypes.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
UCLASS()
|
||||
class UWing_Widget_SearchTypes : public UWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Array of query strings; each may contain *"))
|
||||
FWingJsonArray Queries;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Maximum number of results per query (default 50)"))
|
||||
int32 MaxResults = 50;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Search for widget types that can be added to a Widget Blueprint. "
|
||||
"Returns names for use with Widget_Add."));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
// Validate all entries are strings before running any searches.
|
||||
TArray<FString> QueryStrings;
|
||||
QueryStrings.Reserve(Queries.Array.Num());
|
||||
for (const TSharedPtr<FJsonValue>& QueryVal : Queries.Array)
|
||||
{
|
||||
FString QueryStr;
|
||||
if (!QueryVal->TryGetString(QueryStr))
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT("ERROR: Queries must be an array of strings.\n"));
|
||||
return;
|
||||
}
|
||||
QueryStrings.Add(QueryStr);
|
||||
}
|
||||
|
||||
WingWidgets Widgets;
|
||||
for (const FString& Query : QueryStrings)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("\n=== %s ===\n\n"), *Query);
|
||||
TArray<WingWidgets::Type> Results = Widgets.Search(Query, MaxResults, false);
|
||||
for (const WingWidgets::Type& Entry : Results)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("%s\n"), *Entry.MenuName);
|
||||
}
|
||||
|
||||
if (Results.Num() == 0)
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT("No matching widget types found.\n"));
|
||||
}
|
||||
else if (Results.Num() >= MaxResults)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("WARNING: Reached limit of %d results. You may specify MaxResults.\n"), MaxResults);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -6,17 +6,23 @@
|
||||
|
||||
void WingManual::PrintHandlerPrototype(const FWingHandlerConfig& Handler)
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT("ue-wingman "));
|
||||
WingOut::Stdout.Print(Handler.Name);
|
||||
WingOut::Stdout.Print(TEXT("("));
|
||||
bool bFirst = true;
|
||||
for (TFieldIterator<FProperty> PropIt(Handler.HandlerClass.Get(), EFieldIterationFlags::None); PropIt; ++PropIt)
|
||||
{
|
||||
if (!bFirst) WingOut::Stdout.Print(TEXT(","));
|
||||
bFirst = false;
|
||||
if (PropIt->HasMetaData(TEXT("Optional"))) WingOut::Stdout.Print(TEXT("?"));
|
||||
WingOut::Stdout.Print(PropIt->GetName());
|
||||
FStructProperty* StructProp = CastField<FStructProperty>(*PropIt);
|
||||
const bool bIsRest =
|
||||
StructProp && (StructProp->Struct == FWingRestOfArgv::StaticStruct());
|
||||
if (bIsRest)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT(" [%s...]"), *PropIt->GetName());
|
||||
}
|
||||
else
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT(" %s"), *PropIt->GetName());
|
||||
}
|
||||
}
|
||||
WingOut::Stdout.Print(TEXT(")\n"));
|
||||
WingOut::Stdout.Print(TEXT("\n"));
|
||||
}
|
||||
|
||||
void WingManual::PrintHandlerArguments(const FWingHandlerConfig& Handler)
|
||||
@@ -26,197 +32,193 @@ void WingManual::PrintHandlerArguments(const FWingHandlerConfig& Handler)
|
||||
{
|
||||
FProperty* Prop = *PropIt;
|
||||
FString Name = Prop->GetName();
|
||||
FString Type = UWingTypes::TypeToText(Prop);
|
||||
bool bOptional = Prop->HasMetaData(TEXT("Optional"));
|
||||
const FString& Desc = Prop->GetMetaData(TEXT("Description"));
|
||||
FString Desc = Prop->GetMetaData(TEXT("Description"));
|
||||
if (Desc.IsEmpty()) Desc = TEXT("No documentation");
|
||||
|
||||
if (bOptional)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT(" %s (optional %s)"), *Name, *Type);
|
||||
}
|
||||
else
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT(" %s (%s)"), *Name, *Type);
|
||||
}
|
||||
if (!Desc.IsEmpty()) WingOut::Stdout.Printf(TEXT(" — %s"), *Desc);
|
||||
WingOut::Stdout.Print(TEXT("\n"));
|
||||
WingOut::Stdout.Printf(TEXT(" %s - %s\n"), *Name, *Desc);
|
||||
}
|
||||
}
|
||||
|
||||
void WingManual::PrintHandlerDescription(const FWingHandlerConfig& Handler)
|
||||
{
|
||||
if (Handler.Documentation.IsEmpty()) return;
|
||||
WingOut::Stdout.Print(WingUtils::WrapText(Handler.Documentation, 80, TEXT(" // ")));
|
||||
WingOut::Stdout.Printf(TEXT("\n%s\n\n"), *Handler.Documentation);
|
||||
}
|
||||
|
||||
void WingManual::PrintHandlerHelp(const FWingHandlerConfig& Handler)
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT("\n"));
|
||||
PrintHandlerPrototype(Handler);
|
||||
PrintHandlerArguments(Handler);
|
||||
PrintHandlerDescription(Handler);
|
||||
PrintHandlerPrototype(Handler);
|
||||
PrintHandlerArguments(Handler);
|
||||
PrintHandlerDescription(Handler);
|
||||
WingOut::Stdout.Print(TEXT("\n"));
|
||||
}
|
||||
|
||||
void UWingManualSections::FetcherPaths()
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT(
|
||||
"\n FETCHER PATHS:"
|
||||
"\n"
|
||||
"\n Most commands require you to specify a 'fetcher path'."
|
||||
"\n A fetcher path starts with an asset name, followed by"
|
||||
"\n steps that navigate into the asset. Some Examples:"
|
||||
"\n"
|
||||
"\n /Game/Widgets/WB_Hotkeys,widget:Canvas.122"
|
||||
"\n /Game/Testing/BP_Test,graph:Rescale.Actor,node:K2Node_CallFunction_0,pin:Scale"
|
||||
"\n /Game/Chars/BP_Manny,component:Camera.Boom"
|
||||
"\n"
|
||||
"\n The navigation steps supported are:"
|
||||
"\n"
|
||||
"\n graph — move from a blueprint or material to a graph."
|
||||
"\n node — move from a graph to a graph node"
|
||||
"\n pin — move from a graph node to a pin"
|
||||
"\n component — move from a blueprint to a component"
|
||||
"\n levelblueprint — move from a world to a blueprint"
|
||||
"\n widget — move from a widget blueprint to a widget"
|
||||
"\n structprop — move into a struct property of an object"
|
||||
"\n"
|
||||
"\n Notice that paths use escaped fnames. See the section"
|
||||
"\n on escape sequences in fnames below sfor more information."
|
||||
"\n"
|
||||
"\n Steps do not always require a parameter. For example, materials"
|
||||
"\n only have one graph, so you can just say:"
|
||||
"\n"
|
||||
"\n /Game/Materials/MyMaterial,graph"
|
||||
"\n"
|
||||
));
|
||||
WingOut::Stdout.Print(TEXT(
|
||||
"\n FETCHER PATHS:"
|
||||
"\n"
|
||||
"\n Most commands require you to specify a 'fetcher path'."
|
||||
"\n A fetcher path starts with an asset name, followed by"
|
||||
"\n steps that navigate into the asset. Some Examples:"
|
||||
"\n"
|
||||
"\n /Game/Widgets/WB_Hotkeys,widget:Canvas.122"
|
||||
"\n /Game/Testing/BP_Test,graph:Rescale.Actor,node:K2Node_CallFunction_0,pin:Scale"
|
||||
"\n /Game/Chars/BP_Manny,component:Camera.Boom"
|
||||
"\n"
|
||||
"\n The navigation steps supported are:"
|
||||
"\n"
|
||||
"\n graph — move from a blueprint or material to a graph."
|
||||
"\n node — move from a graph to a graph node"
|
||||
"\n pin — move from a graph node to a pin"
|
||||
"\n component — move from a blueprint to a component"
|
||||
"\n levelblueprint — move from a world to a blueprint"
|
||||
"\n widget — move from a widget blueprint to a widget"
|
||||
"\n structprop — move into a struct property of an object"
|
||||
"\n"
|
||||
"\n Notice that paths use escaped fnames. See the section"
|
||||
"\n on escape sequences in fnames below sfor more information."
|
||||
"\n"
|
||||
"\n Steps do not always require a parameter. For example, materials"
|
||||
"\n only have one graph, so you can just say:"
|
||||
"\n"
|
||||
"\n /Game/Materials/MyMaterial,graph"
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
|
||||
void UWingManualSections::ExpressingTypes()
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT(
|
||||
"\n EXPRESSING TYPES:"
|
||||
"\n"
|
||||
"\n To change the type of a variable, or to express function parameters,"
|
||||
"\n you will use our syntax for types. Here are some valid examples:"
|
||||
"\n"
|
||||
"\n Bool, String, Vector, Rotator, HitResult, Actor, Character,"
|
||||
"\n PlayerController, EBlendMode, EMovementMode, BP_Manny, BP_Quinn,"
|
||||
"\n Array<Int>, Set<String>, Map<Int,Actor>"
|
||||
"\n Soft<ABP_Manny>, Class<Pawn>, SoftClass<Pawn>"
|
||||
"\n"
|
||||
"\n Notice that it's 'Actor', not 'AActor'. Type names are not"
|
||||
"\n case-sensitive. When a blueprint like /Game/Testing/BP_Foo"
|
||||
"\n is used as a type, the typename is just BP_Foo. You can search"
|
||||
"\n for valid types using the TypeName_Search command."
|
||||
"\n"
|
||||
));
|
||||
WingOut::Stdout.Print(TEXT(
|
||||
"\n EXPRESSING TYPES:"
|
||||
"\n"
|
||||
"\n To change the type of a variable, or to express function parameters,"
|
||||
"\n you will use our syntax for types. Here are some valid examples:"
|
||||
"\n"
|
||||
"\n Bool, String, Vector, Rotator, HitResult, Actor, Character,"
|
||||
"\n PlayerController, EBlendMode, EMovementMode, BP_Manny, BP_Quinn,"
|
||||
"\n Array<Int>, Set<String>, Map<Int,Actor>"
|
||||
"\n Soft<ABP_Manny>, Class<Pawn>, SoftClass<Pawn>"
|
||||
"\n"
|
||||
"\n Notice that it's 'Actor', not 'AActor'. Type names are not"
|
||||
"\n case-sensitive. When a blueprint like /Game/Testing/BP_Foo"
|
||||
"\n is used as a type, the typename is just BP_Foo. You can search"
|
||||
"\n for valid types using the TypeName_Search command."
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
|
||||
void UWingManualSections::VariableDeclarations()
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT(
|
||||
"\n VARIABLE DECLARATIONS:"
|
||||
"\n"
|
||||
"\n We have our own syntax for variable declarations: a type,"
|
||||
"\n a name, optional flags, and an optional default value,"
|
||||
"\n always on one line:"
|
||||
"\n"
|
||||
"\n Array<Actor> Actors"
|
||||
"\n Float F (InstanceEditable)"
|
||||
"\n String S = This is the default value"
|
||||
"\n"
|
||||
"\n The commands Variables_Add, Variables_Modify,"
|
||||
"\n and Variables_Remove can be used to edit "
|
||||
"\n blueprint variables, graph local variables, graph input"
|
||||
"\n variables, graph output variables, and custom"
|
||||
"\n event node input variables. Event dispatchers are"
|
||||
"\n also graphs, so they too can be edited."
|
||||
"\n"
|
||||
));
|
||||
WingOut::Stdout.Print(TEXT(
|
||||
"\n VARIABLE DECLARATIONS:"
|
||||
"\n"
|
||||
"\n We have our own syntax for variable declarations:"
|
||||
"\n"
|
||||
"\n kind type name (optional flags) = optional default value"
|
||||
"\n"
|
||||
"\n Kind can be:"
|
||||
"\n"
|
||||
"\n blueprint - eg, instance variables"
|
||||
"\n input - a function argument or macro input"
|
||||
"\n output - a function return value or macro output"
|
||||
"\n local - local variables"
|
||||
"\n"
|
||||
"\n Here are some examples:"
|
||||
"\n"
|
||||
"\n input Array<Actor> Actors"
|
||||
"\n output Float F (InstanceEditable)"
|
||||
"\n blueprint String S = This is the default value"
|
||||
"\n"
|
||||
"\n The commands Variables_Add, Variables_Modify,"
|
||||
"\n and Variables_Remove can be used to edit "
|
||||
"\n blueprint variables, graph local variables, graph input"
|
||||
"\n variables, graph output variables, and custom"
|
||||
"\n event node input variables. Event dispatchers are"
|
||||
"\n also graphs, so they too can be edited."
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
|
||||
void UWingManualSections::EscapeSequencesInFNames()
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT(
|
||||
"\n ESCAPE SEQUENCES IN FNAMES:"
|
||||
"\n"
|
||||
"\n When we output FNames, we use HTML escape sequences for the"
|
||||
"\n following marks: \\\"'(),.:;<=>&, and for certain other characters."
|
||||
"\n We also translate spaces to periods."
|
||||
"\n"
|
||||
"\n When sending FNames to UE Wingman, you *must* escape the marks"
|
||||
"\n listed above, but you *may* escape any character. To send an FName"
|
||||
"\n with a space in it, either use   or a period."
|
||||
"\n"
|
||||
));
|
||||
WingOut::Stdout.Print(TEXT(
|
||||
"\n ESCAPE SEQUENCES IN FNAMES:"
|
||||
"\n"
|
||||
"\n When we output FNames, we use HTML escape sequences for the"
|
||||
"\n following marks: \\\"'(),.:;<=>&, and for certain other characters."
|
||||
"\n We also translate spaces to periods."
|
||||
"\n"
|
||||
"\n When sending FNames to UE Wingman, you *must* escape the marks"
|
||||
"\n listed above, but you *may* escape any character. To send an FName"
|
||||
"\n with a space in it, either use   or a period."
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
|
||||
void UWingManualSections::MaterialEditing()
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT(
|
||||
"\n MATERIAL EDITING:"
|
||||
"\n"
|
||||
"\n We do not expose material expressions directly. Instead, you"
|
||||
"\n will be editing the material graph. However, if you Graph_Dump"
|
||||
"\n a material graph, you will see that the nodes contain"
|
||||
"\n properties which actually come from the material expressions."
|
||||
"\n You can edit these using Details_Set on the node."
|
||||
"\n"
|
||||
"\n Don't overlook custom HLSL nodes. These can accomplish in\n"
|
||||
"\n a single node what would otherwise take many.\n"
|
||||
"\n"
|
||||
));
|
||||
WingOut::Stdout.Print(TEXT(
|
||||
"\n MATERIAL EDITING:"
|
||||
"\n"
|
||||
"\n We do not expose material expressions directly. Instead, you"
|
||||
"\n will be editing the material graph. However, if you Graph_Dump"
|
||||
"\n a material graph, you will see that the nodes contain"
|
||||
"\n properties which actually come from the material expressions."
|
||||
"\n You can edit these using Details_Set on the node."
|
||||
"\n"
|
||||
"\n Don't overlook custom HLSL nodes. These can accomplish in\n"
|
||||
"\n a single node what would otherwise take many.\n"
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
|
||||
void UWingManualSections::NodeContextMenus()
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT(
|
||||
"\n NODE CONTEXT MENUS:"
|
||||
"\n"
|
||||
"\n GraphNode_ShowMenu and GraphNode_ChooseMenu give access"
|
||||
"\n to the node context menu. This menu includes both node"
|
||||
"\n operations and pin operations (e.g. Split Struct Pin,"
|
||||
"\n Add Pin)."
|
||||
"\n"
|
||||
));
|
||||
WingOut::Stdout.Print(TEXT(
|
||||
"\n NODE CONTEXT MENUS:"
|
||||
"\n"
|
||||
"\n GraphNode_ShowMenu and GraphNode_ChooseMenu give access"
|
||||
"\n to the node context menu. This menu includes both node"
|
||||
"\n operations and pin operations (e.g. Split Struct Pin,"
|
||||
"\n Add Pin)."
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
|
||||
void UWingManualSections::VariableGettersAndSetters()
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT(
|
||||
"\n VARIABLE GETTERS AND SETTERS:"
|
||||
"\n"
|
||||
"\n Access to local vars, function parameters, and "
|
||||
"\n blueprint vars is through getter and setter nodes. "
|
||||
"\n These can be found in GraphNode_SearchTypes by "
|
||||
"\n searching for 'Variable'. Some examples:"
|
||||
"\n"
|
||||
"\n SKEL_WB_Menu_C|Variables|Default|GetPlaceTangible"
|
||||
"\n SKEL_WB_Menu_C|Variables|Default|SetPlaceTangible"
|
||||
"\n SKEL_WB_Menu_C|Variables|WB_Menu|GetMenuPanel"
|
||||
"\n"
|
||||
));
|
||||
WingOut::Stdout.Print(TEXT(
|
||||
"\n VARIABLE GETTERS AND SETTERS:"
|
||||
"\n"
|
||||
"\n Access to local vars, function parameters, and "
|
||||
"\n blueprint vars is through getter and setter nodes. "
|
||||
"\n These can be found in GraphNode_SearchTypes by "
|
||||
"\n searching for 'Variable'. Some examples:"
|
||||
"\n"
|
||||
"\n SKEL_WB_Menu_C|Variables|Default|GetPlaceTangible"
|
||||
"\n SKEL_WB_Menu_C|Variables|Default|SetPlaceTangible"
|
||||
"\n SKEL_WB_Menu_C|Variables|WB_Menu|GetMenuPanel"
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
|
||||
void UWingManualSections::ImportantCommands()
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT(
|
||||
"\n IMPORTANT COMMANDS:"
|
||||
"\n"
|
||||
"\n Documentation_Manual: print manual sections"
|
||||
"\n Documentation_Commands: a list of all the main commands"
|
||||
"\n Documentation_CreateAssets: Additional commands that create new assets"
|
||||
"\n Blueprint_Dump: a summary of any blueprint"
|
||||
"\n Graph_Dump: a fairly detailed listing of any Graph"
|
||||
"\n Details_Dump: Dump the details panel for a given object"
|
||||
"\n Details_Set: Manipulate the details panel for a given object"
|
||||
"\n Sequence: Batch commands together for faster execution"
|
||||
"\n"
|
||||
"\n You can use Documentation_Commands(Query=Command,Verbose=true)"
|
||||
"\n to get detailed help for a specific command."
|
||||
"\n"
|
||||
));
|
||||
WingOut::Stdout.Print(TEXT(
|
||||
"\n IMPORTANT COMMANDS:"
|
||||
"\n"
|
||||
"\n Documentation_Manual: print manual sections"
|
||||
"\n Documentation_Commands: A concise list of all ue-wingman commands"
|
||||
"\n Documentation_Command: Detailed documentation for a single ue-wingman command"
|
||||
"\n Documentation_CreateAssets: Additional commands that create new assets"
|
||||
"\n Blueprint_Dump: a summary of any blueprint"
|
||||
"\n Graph_Dump: a fairly detailed listing of any Graph"
|
||||
"\n Details_Dump: Dump the details panel for a given object"
|
||||
"\n Details_Set: Manipulate the details panel for a given object"
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
|
||||
TSet<FName> WingManual::GetSections()
|
||||
@@ -232,7 +234,7 @@ TSet<FName> WingManual::GetSections()
|
||||
void WingManual::PrintSectionNames(const TCHAR *Prefix, const TSet<FName>& Sections, WingOut Output)
|
||||
{
|
||||
if (Sections.IsEmpty()) return;
|
||||
if (Prefix) Output.Print(Prefix);
|
||||
if (Prefix) Output.Print(Prefix);
|
||||
bool bFirst = true;
|
||||
for (const FName& Section : Sections)
|
||||
{
|
||||
@@ -240,7 +242,7 @@ void WingManual::PrintSectionNames(const TCHAR *Prefix, const TSet<FName>& Secti
|
||||
bFirst = false;
|
||||
Output.Printf(TEXT("%s"), *Section.ToString());
|
||||
}
|
||||
if (Prefix) Output.Print(TEXT("\n"));
|
||||
if (Prefix) Output.Print(TEXT("\n"));
|
||||
}
|
||||
|
||||
bool WingManual::PrintSection(FName Section)
|
||||
@@ -256,10 +258,12 @@ void WingManual::Commands(EWingHandlerKind Kind, const FString& Query, bool Verb
|
||||
FString QueryLower = Query.ToLower();
|
||||
FString PrevGroup;
|
||||
|
||||
bool any = false;
|
||||
for (const FWingHandlerConfig& H : UWingServer::AllHandlers())
|
||||
{
|
||||
if (H.Kind != Kind) continue;
|
||||
if (!H.Name.ToLower().Contains(QueryLower)) continue;
|
||||
any = true;
|
||||
|
||||
// Blank line between groups
|
||||
if (!Verbose)
|
||||
@@ -278,4 +282,9 @@ void WingManual::Commands(EWingHandlerKind Kind, const FString& Query, bool Verb
|
||||
else
|
||||
PrintHandlerPrototype(H);
|
||||
}
|
||||
if (!any)
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT("No matching commands. To see a full list, type:\n"));
|
||||
WingOut::Stdout.Print(TEXT(" ue-wingman Documentation_Commands.\n"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,57 +194,6 @@ bool FWingProperty::SetText(FString Value, WingOut Errors) const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FWingProperty::SetJson(const FJsonValue &JsonValue, WingOut Errors) const
|
||||
{
|
||||
if (!CheckEditable(Errors)) return false;
|
||||
|
||||
if (JsonValue.Type == EJson::String)
|
||||
{
|
||||
return SetText(JsonValue.AsString(), Errors);
|
||||
}
|
||||
|
||||
if (JsonValue.Type == EJson::Number)
|
||||
{
|
||||
return SetDouble(JsonValue.AsNumber(), Errors);
|
||||
}
|
||||
|
||||
if (JsonValue.Type == EJson::Boolean)
|
||||
{
|
||||
return SetBool(JsonValue.AsBool(), Errors);
|
||||
}
|
||||
|
||||
if (JsonValue.Type == EJson::Object)
|
||||
{
|
||||
FStructProperty* StructProp = CastField<FStructProperty>(Prop);
|
||||
if (StructProp && (StructProp->Struct == FWingJsonObject::StaticStruct()))
|
||||
{
|
||||
FWingJsonObject Val;
|
||||
Val.Json = JsonValue.AsObject();
|
||||
Prop->SetValue_InContainer(Container, &Val);
|
||||
return true;
|
||||
}
|
||||
PrintExpectsReceived(TEXT("json object"), Errors);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (JsonValue.Type == EJson::Array)
|
||||
{
|
||||
FStructProperty* StructProp = CastField<FStructProperty>(Prop);
|
||||
if (StructProp && (StructProp->Struct == FWingJsonArray::StaticStruct()))
|
||||
{
|
||||
FWingJsonArray Val;
|
||||
Val.Array = JsonValue.AsArray();
|
||||
Prop->SetValue_InContainer(Container, &Val);
|
||||
return true;
|
||||
}
|
||||
PrintExpectsReceived(TEXT("json array"), Errors);
|
||||
return false;
|
||||
}
|
||||
|
||||
PrintExpectsReceived(TEXT("Unrecognized Json Data"), Errors);
|
||||
return false;
|
||||
}
|
||||
|
||||
TOptional<UObject*> FWingProperty::GetObject(WingOut Errors) const
|
||||
{
|
||||
FObjectPropertyBase *OProp = CastField<FObjectPropertyBase>(Prop);
|
||||
@@ -493,56 +442,54 @@ TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, bool Mutable)
|
||||
return Result;
|
||||
}
|
||||
|
||||
|
||||
bool FWingProperty::PopulateFromJson(TArray<FWingProperty>& Props, const FJsonObject& Json, bool AllOptional, WingOut Errors)
|
||||
bool FWingProperty::PopulateFromArgv(TArray<FWingProperty>& Props, TConstArrayView<FString> Argv, WingOut Errors)
|
||||
{
|
||||
bool Ok = true;
|
||||
|
||||
// Build a set of known property names for the unknown-field check.
|
||||
TSet<FName> KnownKeys;
|
||||
for (const FWingProperty& P : Props) KnownKeys.Add(P->GetFName());
|
||||
|
||||
// Check for unknown fields in the JSON
|
||||
for (const auto& KV : Json.Values)
|
||||
int32 ArgIndex = 0;
|
||||
for (int32 PropIndex = 0; PropIndex < Props.Num(); ++PropIndex)
|
||||
{
|
||||
FName Name = WingUtils::CheckInternalizeID(KV.Key, Errors);
|
||||
if (!KnownKeys.Contains(Name))
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Unknown parameter '%s'\n"), *KV.Key);
|
||||
Ok = false;
|
||||
}
|
||||
}
|
||||
FWingProperty& P = Props[PropIndex];
|
||||
FStructProperty* StructProp = CastField<FStructProperty>(P.Prop);
|
||||
const bool bIsRest =
|
||||
StructProp && (StructProp->Struct == FWingRestOfArgv::StaticStruct());
|
||||
|
||||
// Populate each property from JSON
|
||||
for (FWingProperty& P : Props)
|
||||
{
|
||||
FString JsonKey = WingUtils::FormatName(P.Prop);
|
||||
TSharedPtr<FJsonValue> Value = Json.TryGetField(JsonKey);
|
||||
if (!Value)
|
||||
if (bIsRest)
|
||||
{
|
||||
bool Optional = AllOptional || P.Prop->HasMetaData(TEXT("Optional"));
|
||||
if (!Optional)
|
||||
if (PropIndex + 1 != Props.Num())
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Missing required parameter '%s'\n"), *JsonKey);
|
||||
Ok = false;
|
||||
Errors.Printf(TEXT("ERROR: '%s' must be the last parameter\n"),
|
||||
*WingUtils::FormatName(P.Prop));
|
||||
return false;
|
||||
}
|
||||
|
||||
FWingRestOfArgv Rest;
|
||||
for (int32 I = ArgIndex; I < Argv.Num(); ++I)
|
||||
{
|
||||
Rest.Argv.Add(Argv[I]);
|
||||
}
|
||||
P.Prop->SetValue_InContainer(P.Container, &Rest);
|
||||
ArgIndex = Argv.Num();
|
||||
continue;
|
||||
}
|
||||
if (!P.SetJson(*Value, Errors)) Ok = false;
|
||||
}
|
||||
return Ok;
|
||||
}
|
||||
|
||||
bool FWingProperty::PopulateFromJson(TArray<FWingProperty>& Props, const FJsonValue& Json, bool AllOptional, WingOut Errors)
|
||||
{
|
||||
// Make sure they passed in a JSON object.
|
||||
TSharedPtr<FJsonObject> Obj = Json.AsObject();
|
||||
if (Obj == nullptr)
|
||||
if (ArgIndex >= Argv.Num())
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Missing parameter '%s'\n"),
|
||||
*WingUtils::FormatName(P.Prop));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!P.SetText(Argv[ArgIndex], Errors)) return false;
|
||||
ArgIndex++;
|
||||
}
|
||||
|
||||
if (ArgIndex < Argv.Num())
|
||||
{
|
||||
Errors.Printf(TEXT("property data should be stored in a json object\n"));
|
||||
Errors.Printf(TEXT("ERROR: Too many parameters, starting with '%s'\n"),
|
||||
*Argv[ArgIndex]);
|
||||
return false;
|
||||
}
|
||||
return PopulateFromJson(Props, *Obj, AllOptional, Errors);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ void UWingServer::Deinitialize()
|
||||
bShuttingDown = true;
|
||||
for (auto& Msg : PendingMessages)
|
||||
{
|
||||
Msg->Response.SetValue(FString());
|
||||
Msg->Response.SetValue(TArray<uint8>());
|
||||
}
|
||||
PendingMessages.Empty();
|
||||
}
|
||||
@@ -150,7 +150,7 @@ void UWingServer::Tick(float DeltaTime)
|
||||
// If we have a request, process it.
|
||||
if (Request.IsValid())
|
||||
{
|
||||
FString Response = HandleRequest(Request->Line);
|
||||
TArray<uint8> Response = HandleRequest(Request->Request);
|
||||
Request->Response.SetValue(Response);
|
||||
}
|
||||
}
|
||||
@@ -169,71 +169,24 @@ TStatId UWingServer::GetStatId() const
|
||||
// HandleRequest — Given a command, execute it.
|
||||
// ============================================================
|
||||
|
||||
FString UWingServer::HandleRequest(const FString& Line)
|
||||
TArray<uint8> UWingServer::HandleRequest(const TArray<uint8>& RequestBytes)
|
||||
{
|
||||
// Parse the request as JSON before doing anything else.
|
||||
TSharedPtr<FJsonValue> Value;
|
||||
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Line);
|
||||
if (!FJsonSerializer::Deserialize(Reader, Value))
|
||||
return PackageResponses({TEXT("Invalid Json")});
|
||||
|
||||
const TSharedPtr<FJsonObject>* RequestPtr = nullptr;
|
||||
if (!Value->TryGetObject(RequestPtr))
|
||||
return PackageResponses({TEXT("Json must be an object")});
|
||||
TSharedPtr<FJsonObject> Request = *RequestPtr;
|
||||
TArray<FString> Argv;
|
||||
FString ResponseText;
|
||||
|
||||
FString Command;
|
||||
Request->TryGetStringField(TEXT("command"), Command);
|
||||
if (Command == TEXT("Sequence"))
|
||||
if (DeserializeArgv(RequestBytes, Argv))
|
||||
{
|
||||
const TArray<TSharedPtr<FJsonValue>>* Subcommands = nullptr;
|
||||
if (!Request->TryGetArrayField(TEXT("subcommands"), Subcommands))
|
||||
return PackageResponses({TEXT("Sequence requires a 'subcommands' array.")});
|
||||
|
||||
TArray<FString> Responses;
|
||||
Responses.Reserve(Subcommands->Num());
|
||||
for (const TSharedPtr<FJsonValue>& Sub : *Subcommands)
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* SubObjPtr = nullptr;
|
||||
if (!Sub->TryGetObject(SubObjPtr))
|
||||
Responses.Add(TEXT("Subcommand must be a JSON object."));
|
||||
else
|
||||
Responses.Add(HandleJsonRequest(*SubObjPtr));
|
||||
}
|
||||
return PackageResponses(Responses);
|
||||
PreCallHandler();
|
||||
TryCallHandler(Argv);
|
||||
ResponseText = PostCallHandler();
|
||||
}
|
||||
else ResponseText = TEXT("Invalid argv encoding (bug in ue-wingman.py)\n");
|
||||
|
||||
return PackageResponses({HandleJsonRequest(Request)});
|
||||
FTCHARToUTF8 Utf8(*ResponseText);
|
||||
return TArray<uint8>(reinterpret_cast<const uint8*>(Utf8.Get()), Utf8.Length());
|
||||
}
|
||||
|
||||
FString UWingServer::PackageResponses(const TArray<FString>& Responses)
|
||||
{
|
||||
TArray<TSharedPtr<FJsonValue>> Blocks;
|
||||
Blocks.Reserve(Responses.Num());
|
||||
for (const FString& Response : Responses)
|
||||
{
|
||||
// Unreal's JSON writer terminates string serialization at the first
|
||||
// embedded null byte rather than escaping it, which would silently
|
||||
// truncate output. Sanitize null bytes to spaces.
|
||||
FString Sanitized = Response;
|
||||
for (int32 i = 0; i < Sanitized.Len(); ++i)
|
||||
{
|
||||
if (Sanitized[i] == TEXT('\0')) Sanitized[i] = TEXT(' ');
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> Block = MakeShared<FJsonObject>();
|
||||
Block->SetStringField(TEXT("type"), TEXT("text"));
|
||||
Block->SetStringField(TEXT("text"), Sanitized);
|
||||
Blocks.Add(MakeShared<FJsonValueObject>(Block));
|
||||
}
|
||||
|
||||
FString OutJson;
|
||||
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutJson);
|
||||
FJsonSerializer::Serialize(Blocks, Writer);
|
||||
return OutJson;
|
||||
}
|
||||
|
||||
FString UWingServer::HandleJsonRequest(TSharedPtr<FJsonObject> Request)
|
||||
void UWingServer::PreCallHandler()
|
||||
{
|
||||
LogCapture.CapturedErrors.Empty();
|
||||
LogCapture.bEnabled = true;
|
||||
@@ -241,9 +194,10 @@ FString UWingServer::HandleJsonRequest(TSharedPtr<FJsonObject> Request)
|
||||
SuggestedManualSections.Empty();
|
||||
bSuggestHandlerHelp = false;
|
||||
LastHandler = nullptr;
|
||||
}
|
||||
|
||||
TryCallHandler(Request);
|
||||
|
||||
FString UWingServer::PostCallHandler()
|
||||
{
|
||||
Notifier.SendNotifications();
|
||||
LogCapture.bEnabled = false;
|
||||
for (const FString& Msg : LogCapture.CapturedErrors)
|
||||
@@ -269,17 +223,15 @@ FString UWingServer::HandleJsonRequest(TSharedPtr<FJsonObject> Request)
|
||||
return Result;
|
||||
}
|
||||
|
||||
void UWingServer::TryCallHandler(TSharedPtr<FJsonObject> Request)
|
||||
void UWingServer::TryCallHandler(const TArray<FString>& Argv)
|
||||
{
|
||||
// Extract the command from the request.
|
||||
FString Command;
|
||||
if (!Request->TryGetStringField(TEXT("command"), Command))
|
||||
if (Argv.Num() < 1)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("Request does not contain 'command' parameter"));
|
||||
WingOut::Stdout.Printf(TEXT("We recommend sending command='Documentation_Manual'."));
|
||||
WingOut::Stdout.Print(TEXT("Missing command\n"));
|
||||
return;
|
||||
}
|
||||
Request->RemoveField(TEXT("command"));
|
||||
|
||||
FString Command = Argv[0];
|
||||
|
||||
// Find the handler for the specified command.
|
||||
FWingHandlerConfig* Found = FindHandler(Command);
|
||||
@@ -296,9 +248,9 @@ void UWingServer::TryCallHandler(TSharedPtr<FJsonObject> Request)
|
||||
UWingHandler* Handler = Cast<UWingHandler>(HandlerObj.Get());
|
||||
Handler->Configuration = Found;
|
||||
|
||||
// Populate the handler object with the request parameters.
|
||||
// Populate the handler object with argv parameters.
|
||||
TArray<FWingProperty> Props = FWingProperty::GetVisible(Handler, true);
|
||||
if (!FWingProperty::PopulateFromJson(Props, *Request, false, WingOut::Stdout))
|
||||
if (!FWingProperty::PopulateFromArgv(Props, MakeArrayView(Argv).RightChop(1), WingOut::Stdout))
|
||||
{
|
||||
UWingServer::SuggestHandlerHelp();
|
||||
return;
|
||||
@@ -356,102 +308,105 @@ void UWingServer::CleanupFinishedClients()
|
||||
|
||||
void UWingServer::ClientThreadFunc(UWingServer* Server, TSharedPtr<FClientConnection> Client)
|
||||
{
|
||||
constexpr int32 MaxRecvBufBytes = 1024 * 1024;
|
||||
constexpr int32 MinUnusedRecvSpace = 4096;
|
||||
|
||||
FSocket* Socket = Client->Socket;
|
||||
TArray<uint8> RecvBuf;
|
||||
RecvBuf.SetNumUninitialized(MinUnusedRecvSpace);
|
||||
int32 RecvLen = 0;
|
||||
|
||||
WaitForAssetRegistry();
|
||||
|
||||
while (true)
|
||||
TArray<uint8> Request;
|
||||
if (!ReceiveRequest(Socket, Request))
|
||||
{
|
||||
FString Request;
|
||||
if (ExtractRequestFromBuffer(RecvBuf, RecvLen, Request))
|
||||
{
|
||||
FString Response;
|
||||
if (!ProcessRequestOnGameThread(Request, Response))
|
||||
{
|
||||
Client->bDone = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the response back, null-terminated (blocking)
|
||||
FTCHARToUTF8 Utf8(*Response);
|
||||
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;
|
||||
return;
|
||||
}
|
||||
|
||||
TArray<uint8> Response;
|
||||
if (!ProcessRequestOnGameThread(Request, Response))
|
||||
{
|
||||
Client->bDone = true;
|
||||
return;
|
||||
}
|
||||
|
||||
SendAll(Socket, Response.GetData(), Response.Num());
|
||||
Client->bDone = true;
|
||||
}
|
||||
|
||||
bool UWingServer::ExtractRequestFromBuffer(
|
||||
TArray<uint8>& RecvBuf, int32& RecvLen, FString& OutRequest)
|
||||
uint32 UWingServer::UnpackBigEndian(const uint8 *Data)
|
||||
{
|
||||
const uint8* EndOfRequest = static_cast<const uint8*>(
|
||||
memchr(RecvBuf.GetData(), '\0', RecvLen));
|
||||
if (EndOfRequest == nullptr)
|
||||
return
|
||||
((uint32)Data[0] << 24) |
|
||||
((uint32)Data[1] << 16) |
|
||||
((uint32)Data[2] << 8) |
|
||||
(uint32)Data[3];
|
||||
}
|
||||
|
||||
bool UWingServer::DeserializeArgv(
|
||||
const TArray<uint8>& RequestBytes, TArray<FString>& Argv)
|
||||
{
|
||||
Argv.Empty();
|
||||
|
||||
int32 Offset = 0;
|
||||
while (Offset < RequestBytes.Num())
|
||||
{
|
||||
return false;
|
||||
if (RequestBytes.Num() - Offset < 4)
|
||||
{
|
||||
Argv.Empty();
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 Length = UnpackBigEndian(RequestBytes.GetData() + Offset);
|
||||
Offset += 4;
|
||||
|
||||
if ((uint32)(RequestBytes.Num() - Offset) < Length)
|
||||
{
|
||||
Argv.Empty();
|
||||
return false;
|
||||
}
|
||||
|
||||
Argv.Add(FString::ConstructFromPtrSize(
|
||||
reinterpret_cast<const UTF8CHAR*>(RequestBytes.GetData() + Offset),
|
||||
Length));
|
||||
Offset += (int32)Length;
|
||||
}
|
||||
|
||||
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)
|
||||
bool UWingServer::ReceiveRequest(FSocket* Socket, TArray<uint8>& OutRequest)
|
||||
{
|
||||
constexpr int32 MaxRecvBufBytes = 1024 * 1024;
|
||||
constexpr int32 MinUnusedRecvSpace = 4096;
|
||||
constexpr int32 ChunkSize = 8192;
|
||||
|
||||
int32 UnusedSpace = RecvBuf.Num() - RecvLen;
|
||||
if (UnusedSpace < MinUnusedRecvSpace)
|
||||
TArray<uint8> RecvBuf;
|
||||
RecvBuf.Reserve(ChunkSize);
|
||||
|
||||
// Unreal's FSocket API is fundamentally broken: recv cannot
|
||||
// differentiate between a socket that has been cleanly closed
|
||||
// and a socket that has had an error. So we have no choice
|
||||
// but to just read until recv returns false (which could be a
|
||||
// clean close or an error). Then, we check if we have a cleanly
|
||||
// encoded payload: if so, we assume everything is fine.
|
||||
while (true)
|
||||
{
|
||||
if (RecvBuf.Num() >= MaxRecvBufBytes)
|
||||
uint8 Temp[ChunkSize];
|
||||
int32 BytesRead = 0;
|
||||
if (!Socket->Recv(Temp, ChunkSize, BytesRead))
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (BytesRead <= 0) break;
|
||||
if (RecvBuf.Num() + BytesRead > MaxRecvBufBytes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
RecvBuf.SetNumUninitialized(RecvBuf.Num() * 2);
|
||||
UnusedSpace = RecvBuf.Num() - RecvLen;
|
||||
RecvBuf.Append(Temp, BytesRead);
|
||||
}
|
||||
|
||||
int32 BytesRead = 0;
|
||||
if (!Socket->Recv(RecvBuf.GetData() + RecvLen, UnusedSpace, BytesRead))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (BytesRead <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (RecvBuf.Num() < 4) return false;
|
||||
uint32 Size = UnpackBigEndian(RecvBuf.GetData());
|
||||
if ((uint32)RecvBuf.Num() != (4u + Size)) return false;
|
||||
RecvBuf.RemoveAt(0, 4);
|
||||
|
||||
RecvLen += BytesRead;
|
||||
OutRequest = MoveTemp(RecvBuf);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -471,13 +426,13 @@ bool UWingServer::SendAll(FSocket* Socket, const uint8* Data, int32 BytesToSend)
|
||||
}
|
||||
|
||||
bool UWingServer::ProcessRequestOnGameThread(
|
||||
const FString& Request, FString& Response)
|
||||
const TArray<uint8>& Request, TArray<uint8>& Response)
|
||||
{
|
||||
// Enqueue the message for game-thread processing.
|
||||
TSharedPtr<UWingServer::FPendingMessage> Msg =
|
||||
MakeShared<UWingServer::FPendingMessage>();
|
||||
Msg->Line = Request;
|
||||
TFuture<FString> Future = Msg->Response.GetFuture();
|
||||
Msg->Request = Request;
|
||||
TFuture<TArray<uint8>> Future = Msg->Response.GetFuture();
|
||||
|
||||
{
|
||||
FScopeLock Lock(&GWingServer->Mutex);
|
||||
|
||||
@@ -82,7 +82,7 @@ bool WingVariableList::CheckSanity(const TSet<FName> &GoodFlags, bool Allow, Win
|
||||
{
|
||||
if ((!Allow) && (!Variables.IsEmpty()))
|
||||
{
|
||||
Errors.Printf(TEXT("In this context, %s must be empty."), ListName);
|
||||
Errors.Printf(TEXT("This object does not support %s.\n"), ListName);
|
||||
return false;
|
||||
}
|
||||
for (const Var &Variable : Variables)
|
||||
@@ -112,116 +112,6 @@ bool WingVariableList::CheckSanity(const TSet<FName> &GoodFlags, bool Allow, Win
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WingVariableList::ParseString(const FString &Input, WingOut Errors)
|
||||
{
|
||||
Variables.Empty();
|
||||
|
||||
TArray<FString> Lines;
|
||||
Input.ParseIntoArrayLines(Lines);
|
||||
|
||||
for (const FString& Line : Lines)
|
||||
{
|
||||
WingTokenizer Tok(Line);
|
||||
if (Tok.NextType() == 0) continue;
|
||||
Var V;
|
||||
V.DefaultSpecified = false;
|
||||
if (!ParseOneVariable(Tok, V, Errors)) return false;
|
||||
Variables.Add(MoveTemp(V));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WingVariableList::ParseNamesString(const FString &Input, WingOut Errors)
|
||||
{
|
||||
Variables.Empty();
|
||||
WingTokenizer Tok(Input);
|
||||
while (Tok.TokenIs(Tok.Identifier))
|
||||
{
|
||||
FName Name = Tok.NextName();
|
||||
Var V;
|
||||
V.Name = Name;
|
||||
Variables.Add(V);
|
||||
V.DefaultSpecified = false;
|
||||
Tok.Advance();
|
||||
if (Tok.TokenIs(',')) Tok.Advance();
|
||||
}
|
||||
if (!Tok.TokenIs(0))
|
||||
{
|
||||
Tok.SaveCursor(NAME_None);
|
||||
Errors.Printf(TEXT("Unexpected token %s in variable list"),
|
||||
*FString(Tok.GetRange(NAME_None, 1)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WingVariableList::ParseOneVariable(WingTokenizer &Tok, Var &V, WingOut Errors)
|
||||
{
|
||||
// Parse type.
|
||||
UWingTypes::Requirements Req;
|
||||
Req.BlueprintType = true;
|
||||
Req.Blueprintable = false;
|
||||
Req.AllowContainer = true;
|
||||
if (!UWingTypes::TextToType(Tok, V.Type, Req, false, Errors))
|
||||
return false;
|
||||
|
||||
// Parse name.
|
||||
if (Tok.NextType() != Tok.Identifier)
|
||||
{
|
||||
Errors.Print(TEXT("ERROR: Expected variable name after type\n"));
|
||||
return false;
|
||||
}
|
||||
V.Name = Tok.NextName();
|
||||
Tok.Advance();
|
||||
|
||||
// Parse optional flags: (flag1, flag2, ...)
|
||||
if (Tok.TokenIs('('))
|
||||
{
|
||||
if (!ParseVariableFlags(Tok, V.Flags, Errors)) return false;
|
||||
}
|
||||
|
||||
// Parse optional default value: = rest-of-line
|
||||
if (Tok.NextType() == Tok.RestOfLine)
|
||||
{
|
||||
V.DefaultSpecified = true;
|
||||
V.DefaultValue = FString(Tok.NextRest().TrimStartAndEnd());
|
||||
Tok.Advance();
|
||||
}
|
||||
|
||||
// Should be at end of line.
|
||||
if (Tok.NextType() != 0)
|
||||
{
|
||||
Tok.SaveCursor(NAME_None);
|
||||
Errors.Printf(TEXT("ERROR: Unexpected token after variable declaration: '%s'\n"),
|
||||
*FString(Tok.GetRange(NAME_None, 1)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WingVariableList::ParseVariableFlags(WingTokenizer &Tok, TSet<FName> &Out, WingOut Errors)
|
||||
{
|
||||
Tok.Advance(); // Step over open-paren
|
||||
while (Tok.TokenIs(Tok.Identifier))
|
||||
{
|
||||
Out.Add(Tok.NextName());
|
||||
Tok.Advance();
|
||||
// Commas are optional.
|
||||
if (Tok.TokenIs(',')) Tok.Advance();
|
||||
}
|
||||
if (!Tok.TokenIs(')'))
|
||||
{
|
||||
Tok.SaveCursor(NAME_None);
|
||||
Errors.Printf(TEXT("ERROR: flag list contains invalid token '%s'\n"),
|
||||
*FString(Tok.GetRange(NAME_None, 1)));
|
||||
return false;
|
||||
}
|
||||
Tok.Advance(); // Step over close-paren
|
||||
return true;
|
||||
}
|
||||
|
||||
void WingVariables::Empty()
|
||||
{
|
||||
BlueprintVariables.Empty();
|
||||
@@ -254,6 +144,107 @@ void WingVariables::Print(WingOut Out)
|
||||
OutputVariables.Print(Out);
|
||||
}
|
||||
|
||||
WingVariableList *WingVariables::GetList(FName Name)
|
||||
{
|
||||
if (Name == TEXT("blueprint")) return &BlueprintVariables;
|
||||
if (Name == TEXT("input")) return &InputVariables;
|
||||
if (Name == TEXT("output")) return &OutputVariables;
|
||||
if (Name == TEXT("local")) return &LocalVariables;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool WingVariables::ParseOneVariable(WingTokenizer &Tok, FName &Kind, Var &V, bool NameOnly, WingOut Errors)
|
||||
{
|
||||
// Parse Kind.
|
||||
if (GetList(Tok.NextName()) == nullptr)
|
||||
{
|
||||
Errors.Print(TEXT("ERROR: Variable description should start with 'blueprint', 'input', 'output', or 'local'"));
|
||||
return false;
|
||||
}
|
||||
Kind = Tok.NextName();
|
||||
Tok.Advance();
|
||||
|
||||
// Parse type.
|
||||
if (!NameOnly)
|
||||
{
|
||||
UWingTypes::Requirements Req;
|
||||
Req.BlueprintType = true;
|
||||
Req.Blueprintable = false;
|
||||
Req.AllowContainer = true;
|
||||
if (!UWingTypes::TextToType(Tok, V.Type, Req, false, Errors))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse name.
|
||||
if (Tok.NextType() != Tok.Identifier)
|
||||
{
|
||||
Errors.Print(TEXT("ERROR: Expected variable name after type\n"));
|
||||
return false;
|
||||
}
|
||||
V.Name = Tok.NextName();
|
||||
Tok.Advance();
|
||||
|
||||
// Parse optional flags: (flag1, flag2, ...)
|
||||
if ((!NameOnly) && Tok.TokenIs('('))
|
||||
{
|
||||
if (!ParseVariableFlags(Tok, V.Flags, Errors)) return false;
|
||||
}
|
||||
|
||||
// Parse optional default value: = rest-of-line
|
||||
if (!NameOnly && (Tok.NextType() == Tok.RestOfLine))
|
||||
{
|
||||
V.DefaultSpecified = true;
|
||||
V.DefaultValue = FString(Tok.NextRest().TrimStartAndEnd());
|
||||
Tok.Advance();
|
||||
}
|
||||
|
||||
// Should be at end of line.
|
||||
if (Tok.NextType() != 0)
|
||||
{
|
||||
Tok.SaveCursor(NAME_None);
|
||||
Errors.Printf(TEXT("ERROR: Unexpected token after variable declaration: '%s'\n"),
|
||||
*FString(Tok.GetRange(NAME_None, 1)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WingVariables::ParseVariableFlags(WingTokenizer &Tok, TSet<FName> &Out, WingOut Errors)
|
||||
{
|
||||
Tok.Advance(); // Step over open-paren
|
||||
while (Tok.TokenIs(Tok.Identifier))
|
||||
{
|
||||
Out.Add(Tok.NextName());
|
||||
Tok.Advance();
|
||||
// Commas are optional.
|
||||
if (Tok.TokenIs(',')) Tok.Advance();
|
||||
}
|
||||
if (!Tok.TokenIs(')'))
|
||||
{
|
||||
Tok.SaveCursor(NAME_None);
|
||||
Errors.Printf(TEXT("ERROR: flag list contains invalid token '%s'\n"),
|
||||
*FString(Tok.GetRange(NAME_None, 1)));
|
||||
return false;
|
||||
}
|
||||
Tok.Advance(); // Step over close-paren
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WingVariables::Parse(const TArray<FString> &Vars, bool NameOnly, WingOut Errors)
|
||||
{
|
||||
for (const FString& Onevar : Vars)
|
||||
{
|
||||
WingTokenizer Tok(Onevar);
|
||||
FName Kind;
|
||||
Var V;
|
||||
if (!ParseOneVariable(Tok, Kind, V, NameOnly, Errors)) return false;
|
||||
WingVariableList *List = GetList(Kind);
|
||||
List->Add(V);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void WingVariables::Load(WingOut Errors)
|
||||
{
|
||||
Empty();
|
||||
|
||||
@@ -62,32 +62,18 @@ public:
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Json wrappers.
|
||||
//
|
||||
// Normally, the json request is automatically used to
|
||||
// populate the properties of the handler, so the handler
|
||||
// doesn't have to deal with json. However, in a few cases,
|
||||
// the handler actually does want to see some json. These
|
||||
// wrappers allow a handler to request raw json data instead
|
||||
// of pre-processed values.
|
||||
// A simple type to store the remaining arguments in
|
||||
// an Argv Array.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
USTRUCT()
|
||||
struct FWingJsonObject
|
||||
struct FWingRestOfArgv
|
||||
{
|
||||
GENERATED_BODY()
|
||||
TSharedPtr<FJsonObject> Json;
|
||||
};
|
||||
|
||||
// Marker struct for handler parameters that accept a JSON array.
|
||||
// PopulateFromJson stashes the actual JSON array into the Array field.
|
||||
//
|
||||
USTRUCT()
|
||||
struct FWingJsonArray
|
||||
{
|
||||
GENERATED_BODY()
|
||||
TArray<TSharedPtr<FJsonValue>> Array;
|
||||
UPROPERTY()
|
||||
TArray<FString> Argv;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
@@ -200,3 +186,4 @@ public:
|
||||
|
||||
bool Editable;
|
||||
};
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ struct FWingProperty
|
||||
bool SetInt64(int64 I, WingOut Errors) const;
|
||||
bool SetBool(bool B, WingOut Errors) const;
|
||||
bool SetText(FString Value, WingOut Errors) const;
|
||||
bool SetJson(const FJsonValue &Value, WingOut Errors) const;
|
||||
|
||||
// Fetch a value. If an error occurs such as a type
|
||||
// mismatch, returns an empty optional and prints an
|
||||
@@ -86,6 +85,7 @@ struct FWingProperty
|
||||
// If mutable is false, all properties will be marked non-editable.
|
||||
//
|
||||
static TArray<FWingProperty> GetVisible(UObject *Obj, void *Container, UStruct *Struct, bool Mutable);
|
||||
static bool PopulateFromArgv(TArray<FWingProperty>& Props, TConstArrayView<FString> Argv, WingOut Errors);
|
||||
|
||||
// Convenience versions of GetAll and GetVisible for UObjects.
|
||||
//
|
||||
@@ -121,16 +121,6 @@ struct FWingProperty
|
||||
//
|
||||
static TArray<FWingProperty> GetDetails(UObject* Obj, bool Mutable);
|
||||
|
||||
// Functions to populate properties from a JSON object.
|
||||
//
|
||||
static bool PopulateFromJson(TArray<FWingProperty>& Props, const FJsonObject& Json,
|
||||
bool AllOptional, WingOut Errors);
|
||||
static bool PopulateFromJson(TArray<FWingProperty>& Props, const FJsonValue& Json,
|
||||
bool AllOptional, WingOut Errors);
|
||||
|
||||
// Functions to populate properties from a JSON object.
|
||||
//
|
||||
|
||||
private:
|
||||
static bool IsUnsigned(FNumericProperty* Prop);
|
||||
static bool IsPinTypeProperty(FProperty *Prop);
|
||||
|
||||
@@ -62,9 +62,6 @@ public:
|
||||
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; }
|
||||
|
||||
/** Package a list of response texts into a single serialized JSON content-block array. */
|
||||
static FString PackageResponses(const TArray<FString>& Responses);
|
||||
|
||||
private:
|
||||
static UWingServer* GWingServer;
|
||||
|
||||
@@ -79,10 +76,11 @@ private:
|
||||
FDelegateHandle LoadingPhasesCompleteHandle;
|
||||
FWingHandlerConfig* FindHandler(const FString& Name);
|
||||
|
||||
// Handle a complete JSON line and return the response JSON
|
||||
FString HandleRequest(const FString& Line);
|
||||
FString HandleJsonRequest(TSharedPtr<FJsonObject> Request);
|
||||
void TryCallHandler(TSharedPtr<FJsonObject> Request);
|
||||
// Handle a complete request and return the response bytes.
|
||||
TArray<uint8> HandleRequest(const TArray<uint8>& RequestBytes);
|
||||
void PreCallHandler();
|
||||
FString PostCallHandler();
|
||||
void TryCallHandler(const TArray<FString>& Argv);
|
||||
|
||||
// ----- TCP server -----
|
||||
FSocket* ListenSocket = nullptr;
|
||||
@@ -99,22 +97,23 @@ private:
|
||||
TArray<TSharedPtr<FClientConnection>> Clients;
|
||||
void AcceptNewConnections();
|
||||
void CleanupFinishedClients();
|
||||
static uint32 UnpackBigEndian(const uint8 *Data);
|
||||
static bool DeserializeArgv(
|
||||
const TArray<uint8>& RequestBytes, TArray<FString>& Argv);
|
||||
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 ReceiveRequest(
|
||||
FSocket* Socket, TArray<uint8>& OutRequest);
|
||||
static bool SendAll(FSocket* Socket, const uint8* Data, int32 BytesToSend);
|
||||
static bool ProcessRequestOnGameThread(
|
||||
const FString& Request, FString& Response);
|
||||
const TArray<uint8>& Request, TArray<uint8>& Response);
|
||||
static void WaitForAssetRegistry();
|
||||
|
||||
// ----- The Critical Section -----
|
||||
struct FPendingMessage
|
||||
{
|
||||
FString Line;
|
||||
TPromise<FString> Response;
|
||||
FPendingMessage() : Response(TPromise<FString>()) {}
|
||||
TArray<uint8> Request;
|
||||
TPromise<TArray<uint8>> Response;
|
||||
FPendingMessage() : Response(TPromise<TArray<uint8>>()) {}
|
||||
};
|
||||
FCriticalSection Mutex;
|
||||
TArray<TSharedPtr<FPendingMessage>> PendingMessages;
|
||||
|
||||
@@ -57,6 +57,9 @@ public:
|
||||
// Empty the variable list.
|
||||
void Empty() { Variables.Empty(); }
|
||||
|
||||
// Add a variable.
|
||||
void Add(const Var &Var) { Variables.Add(Var); }
|
||||
|
||||
// Return true if the variables are empty.
|
||||
bool IsEmpty() { return Variables.IsEmpty(); }
|
||||
|
||||
@@ -72,21 +75,11 @@ public:
|
||||
// Check the sanity of the vars in the array. If allow
|
||||
// is false, then no variables are allowed in the array.
|
||||
bool CheckSanity(const TSet<FName> &GoodFlags, bool Allow, WingOut Errors);
|
||||
|
||||
// Parse variables from a string.
|
||||
bool ParseString(const FString &Input, WingOut Errors);
|
||||
|
||||
// Parse variable names only from a string.
|
||||
bool ParseNamesString(const FString &Input, WingOut Errors);
|
||||
|
||||
private:
|
||||
bool ParseOneVariable(WingTokenizer &Tok, Var &V, WingOut Errors);
|
||||
bool ParseVariableFlags(WingTokenizer &Tok, TSet<FName> &Out, WingOut Errors);
|
||||
};
|
||||
|
||||
class WingVariables
|
||||
{
|
||||
public:
|
||||
public:
|
||||
using Var = WingVariableList::Var;
|
||||
WingVariables() {}
|
||||
|
||||
@@ -125,6 +118,10 @@ public:
|
||||
|
||||
void Print(WingOut Out);
|
||||
|
||||
// Parse variables.
|
||||
|
||||
bool Parse(const TArray<FString> &Vars, bool NameOnly, WingOut Errors);
|
||||
|
||||
// Load: clear the workspace, then
|
||||
// copy everything from the backing store into the workspace.
|
||||
|
||||
@@ -193,4 +190,8 @@ private:
|
||||
void AddUserPinInfo(const Var &V, EEdGraphPinDirection Dir, UK2Node_EditablePinBase *Node);
|
||||
|
||||
bool ErrorNoBackingStore(WingOut Errors);
|
||||
|
||||
bool ParseVariableFlags(WingTokenizer &Tok, TSet<FName> &Out, WingOut Errors);
|
||||
bool ParseOneVariable(WingTokenizer &Tok, FName &Kind, Var &V, bool NameOnly, WingOut Errors);
|
||||
WingVariableList *GetList(FName Name);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user