Back in business

This commit is contained in:
2026-04-04 02:21:04 -04:00
parent e9fff6599a
commit 7c884e84cb
5 changed files with 39 additions and 48 deletions

View File

@@ -23,7 +23,7 @@ struct FSpawnNodeEntry
GENERATED_BODY() GENERATED_BODY()
UPROPERTY() UPROPERTY()
FString ActionName; FString Type;
UPROPERTY() UPROPERTY()
int32 PosX = 0; int32 PosX = 0;
@@ -70,8 +70,8 @@ public:
for (const TSharedPtr<FJsonValue>& Elt : Nodes.Array) for (const TSharedPtr<FJsonValue>& Elt : Nodes.Array)
{ {
if (!FWingProperty::PopulateFromJson(Props, *Elt, false, WingOut::Stdout)) return; if (!FWingProperty::PopulateFromJson(Props, *Elt, false, WingOut::Stdout)) return;
TArray<FWingGraphAction*> Results = GraphActions.Search(Entry.ActionName, 2, true); TArray<FWingGraphAction*> Results = GraphActions.Search(Entry.Type, 2, true);
if (!WingUtils::CheckExactlyOneNamed(Results.Num(), TEXT("node type"), Entry.ActionName, WingOut::Stdout)) return; if (!WingUtils::CheckExactlyOneNamed(Results.Num(), TEXT("node type"), Entry.Type, WingOut::Stdout)) return;
Entry.Action = Results[0]; Entry.Action = Results[0];
Entries.Add(Entry); Entries.Add(Entry);
} }
@@ -82,11 +82,11 @@ public:
UEdGraphNode* NewNode = Entry.Action->Execute(FVector2D(Entry.PosX, Entry.PosY)); UEdGraphNode* NewNode = Entry.Action->Execute(FVector2D(Entry.PosX, Entry.PosY));
if (NewNode) if (NewNode)
{ {
WingOut::Stdout.Printf(TEXT("Spawned: %s %s\n"), *Entry.ActionName, *WingUtils::FormatName(NewNode)); WingOut::Stdout.Printf(TEXT("Spawned: %s %s\n"), *Entry.Type, *WingUtils::FormatName(NewNode));
} }
else else
{ {
WingOut::Stdout.Printf(TEXT("FAILED: %s\n"), *Entry.ActionName); WingOut::Stdout.Printf(TEXT("FAILED: %s\n"), *Entry.Type);
continue; continue;
} }
} }

View File

@@ -347,11 +347,10 @@ FString FWingProperty::GetCategory() const
void FWingProperty::GetAll(FWingStructAndUStruct Obj, EPropertyFlags Flags, TArray<FWingProperty> &Props) void FWingProperty::GetAll(FWingStructAndUStruct Obj, EPropertyFlags Flags, TArray<FWingProperty> &Props)
{ {
TArray<FWingProperty> Result;
for (TFieldIterator<FProperty> It(Obj.UStructPtr); It; ++It) for (TFieldIterator<FProperty> It(Obj.UStructPtr); It; ++It)
{ {
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue; if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
Result.Add(FWingProperty(*It, Obj.StructPtr)); Props.Add(FWingProperty(*It, Obj.StructPtr));
} }
} }
@@ -444,24 +443,16 @@ TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, EPropertyFlags Fla
} }
bool FWingProperty::PopulateFromJson(TArray<FWingProperty>& Props, const FJsonValue& Json, bool AllOptional, WingOut Errors) bool FWingProperty::PopulateFromJson(TArray<FWingProperty>& Props, const FJsonObject& Json, bool AllOptional, WingOut Errors)
{ {
bool Ok = true; bool Ok = true;
// Make sure they passed in a JSON object.
TSharedPtr<FJsonObject> Obj = Json.AsObject();
if (Obj == nullptr)
{
Errors.Printf(TEXT("property data should be stored in a json object\n"));
return false;
}
// Build a set of known property names for the unknown-field check. // Build a set of known property names for the unknown-field check.
TSet<FName> KnownKeys; TSet<FName> KnownKeys;
for (const FWingProperty& P : Props) KnownKeys.Add(P->GetFName()); for (const FWingProperty& P : Props) KnownKeys.Add(P->GetFName());
// Check for unknown fields in the JSON // Check for unknown fields in the JSON
for (const auto& KV : Obj->Values) for (const auto& KV : Json.Values)
{ {
FName Name = WingUtils::CheckInternalizeID(KV.Key, Errors); FName Name = WingUtils::CheckInternalizeID(KV.Key, Errors);
if (!KnownKeys.Contains(Name)) if (!KnownKeys.Contains(Name))
@@ -475,7 +466,7 @@ bool FWingProperty::PopulateFromJson(TArray<FWingProperty>& Props, const FJsonVa
for (FWingProperty& P : Props) for (FWingProperty& P : Props)
{ {
FString JsonKey = WingUtils::FormatName(P.Prop); FString JsonKey = WingUtils::FormatName(P.Prop);
TSharedPtr<FJsonValue> Value = Obj->TryGetField(JsonKey); TSharedPtr<FJsonValue> Value = Json.TryGetField(JsonKey);
if (!Value) if (!Value)
{ {
bool Optional = AllOptional || P.Prop->HasMetaData(TEXT("Optional")); bool Optional = AllOptional || P.Prop->HasMetaData(TEXT("Optional"));
@@ -491,6 +482,18 @@ bool FWingProperty::PopulateFromJson(TArray<FWingProperty>& Props, const FJsonVa
return Ok; 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)
{
Errors.Printf(TEXT("property data should be stored in a json object\n"));
return false;
}
return PopulateFromJson(Props, *Obj, AllOptional, Errors);
}
bool FWingProperty::IsPinTypeProperty(FProperty* Prop) bool FWingProperty::IsPinTypeProperty(FProperty* Prop)
{ {

View File

@@ -1,6 +1,6 @@
#include "WingServer.h" #include "WingServer.h"
#include "WingHandler.h" #include "WingHandler.h"
#include "WingPropHandle.h" #include "WingProperty.h"
#include "WingManual.h" #include "WingManual.h"
#include "WingLogCapture.h" #include "WingLogCapture.h"
#include "WingUtils.h" #include "WingUtils.h"
@@ -328,9 +328,8 @@ void UWingServer::TryCallHandler(const FString &Line)
Handler->ConfigurationObject = Found->Config.Get(); Handler->ConfigurationObject = Found->Config.Get();
// Populate the handler object with the request parameters. // Populate the handler object with the request parameters.
WingPropHandle Props; TArray<FWingProperty> Props = FWingProperty::GetAll(Handler, CPF_Edit);
WingPropHandle::Handles Handles = Props.AllProperties(HandlerObj.Get(), true); if (!FWingProperty::PopulateFromJson(Props, *Request, false, WingOut::Stdout))
if (!WingPropHandle::PopulateFromJson(Handles, *Request, false, WingOut::Stdout))
{ {
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
return; return;

View File

@@ -103,6 +103,8 @@ struct FWingProperty
// Functions to populate properties from a JSON object. // 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, static bool PopulateFromJson(TArray<FWingProperty>& Props, const FJsonValue& Json,
bool AllOptional, WingOut Errors); bool AllOptional, WingOut Errors);

View File

@@ -3,7 +3,8 @@
Human-friendly MCP test client. Human-friendly MCP test client.
Usage: ue-wingman.py <command> [key=value ...] Usage: ue-wingman.py <command> [key=value ...]
ue-wingman.py (reads JSON with "command" from stdin)
Values starting with '[' or '{' are parsed as JSON.
""" """
import sys import sys
@@ -18,32 +19,18 @@ TIMEOUT = 120
def main(): def main():
args = sys.argv[1:] args = sys.argv[1:]
if not args: if not args:
# No arguments: read a complete JSON object from stdin. print("Usage: ue-wingman.py <command> [key=value ...]")
# The JSON must already contain a "command" key.
decoder = json.JSONDecoder()
raw = ""
msg = None
for line in sys.stdin:
raw += line
stripped = raw.strip()
try:
msg, _ = decoder.raw_decode(stripped)
break
except json.JSONDecodeError as e:
if e.pos < len(stripped):
print(f"Malformed JSON: {e.msg}")
sys.exit(1) sys.exit(1)
continue
if msg is None:
print("Could not parse a complete JSON object from stdin")
sys.exit(1)
if "command" not in msg:
print("JSON object must contain a \"command\" key")
sys.exit(1)
else:
msg = {"command": args[0]} msg = {"command": args[0]}
for arg in args[1:]: for arg in args[1:]:
key, _, value = arg.partition("=") key, _, value = arg.partition("=")
if value and value[0] in ('[', '{'):
try:
value = json.loads(value)
except json.JSONDecodeError as e:
print(f"Bad JSON in {key}: {e.msg}")
sys.exit(1)
msg[key] = value msg[key] = value
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)