From 7c884e84cbb344f5ad60200a2645823fb5e512a9 Mon Sep 17 00:00:00 2001 From: jyelon Date: Sat, 4 Apr 2026 02:21:04 -0400 Subject: [PATCH] Back in business --- .../UEWingman/Handlers/GraphNode_Create.h | 10 ++--- .../Source/UEWingman/Private/WingProperty.cpp | 29 +++++++------- .../Source/UEWingman/Private/WingServer.cpp | 7 ++-- .../Source/UEWingman/Public/WingProperty.h | 2 + Plugins/UEWingman/ue-wingman.py | 39 +++++++------------ 5 files changed, 39 insertions(+), 48 deletions(-) diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_Create.h index 03e7415e..98d3fb56 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_Create.h @@ -23,7 +23,7 @@ struct FSpawnNodeEntry GENERATED_BODY() UPROPERTY() - FString ActionName; + FString Type; UPROPERTY() int32 PosX = 0; @@ -70,8 +70,8 @@ public: for (const TSharedPtr& Elt : Nodes.Array) { if (!FWingProperty::PopulateFromJson(Props, *Elt, false, WingOut::Stdout)) return; - TArray Results = GraphActions.Search(Entry.ActionName, 2, true); - if (!WingUtils::CheckExactlyOneNamed(Results.Num(), TEXT("node type"), Entry.ActionName, WingOut::Stdout)) return; + TArray 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); } @@ -82,11 +82,11 @@ public: UEdGraphNode* NewNode = Entry.Action->Execute(FVector2D(Entry.PosX, Entry.PosY)); 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 { - WingOut::Stdout.Printf(TEXT("FAILED: %s\n"), *Entry.ActionName); + WingOut::Stdout.Printf(TEXT("FAILED: %s\n"), *Entry.Type); continue; } } diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp index 9e2a3bf7..491505f3 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp @@ -347,11 +347,10 @@ FString FWingProperty::GetCategory() const void FWingProperty::GetAll(FWingStructAndUStruct Obj, EPropertyFlags Flags, TArray &Props) { - TArray Result; for (TFieldIterator It(Obj.UStructPtr); It; ++It) { 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::GetDetails(UObject* Obj, EPropertyFlags Fla } -bool FWingProperty::PopulateFromJson(TArray& Props, const FJsonValue& Json, bool AllOptional, WingOut Errors) +bool FWingProperty::PopulateFromJson(TArray& Props, const FJsonObject& Json, bool AllOptional, WingOut Errors) { bool Ok = true; - // Make sure they passed in a JSON object. - TSharedPtr 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. TSet KnownKeys; for (const FWingProperty& P : Props) KnownKeys.Add(P->GetFName()); // 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); if (!KnownKeys.Contains(Name)) @@ -475,7 +466,7 @@ bool FWingProperty::PopulateFromJson(TArray& Props, const FJsonVa for (FWingProperty& P : Props) { FString JsonKey = WingUtils::FormatName(P.Prop); - TSharedPtr Value = Obj->TryGetField(JsonKey); + TSharedPtr Value = Json.TryGetField(JsonKey); if (!Value) { bool Optional = AllOptional || P.Prop->HasMetaData(TEXT("Optional")); @@ -491,6 +482,18 @@ bool FWingProperty::PopulateFromJson(TArray& Props, const FJsonVa return Ok; } +bool FWingProperty::PopulateFromJson(TArray& Props, const FJsonValue& Json, bool AllOptional, WingOut Errors) +{ + // Make sure they passed in a JSON object. + TSharedPtr 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) { diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp index 1b5eaf4a..6134f13d 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp @@ -1,6 +1,6 @@ #include "WingServer.h" #include "WingHandler.h" -#include "WingPropHandle.h" +#include "WingProperty.h" #include "WingManual.h" #include "WingLogCapture.h" #include "WingUtils.h" @@ -328,9 +328,8 @@ void UWingServer::TryCallHandler(const FString &Line) Handler->ConfigurationObject = Found->Config.Get(); // Populate the handler object with the request parameters. - WingPropHandle Props; - WingPropHandle::Handles Handles = Props.AllProperties(HandlerObj.Get(), true); - if (!WingPropHandle::PopulateFromJson(Handles, *Request, false, WingOut::Stdout)) + TArray Props = FWingProperty::GetAll(Handler, CPF_Edit); + if (!FWingProperty::PopulateFromJson(Props, *Request, false, WingOut::Stdout)) { UWingServer::SuggestManual(WingManual::Section::HandlerHelp); return; diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h b/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h index a9bd7cc7..bdf03695 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h @@ -103,6 +103,8 @@ struct FWingProperty // Functions to populate properties from a JSON object. // + static bool PopulateFromJson(TArray& Props, const FJsonObject& Json, + bool AllOptional, WingOut Errors); static bool PopulateFromJson(TArray& Props, const FJsonValue& Json, bool AllOptional, WingOut Errors); diff --git a/Plugins/UEWingman/ue-wingman.py b/Plugins/UEWingman/ue-wingman.py index 31e75185..8ec92525 100755 --- a/Plugins/UEWingman/ue-wingman.py +++ b/Plugins/UEWingman/ue-wingman.py @@ -3,7 +3,8 @@ Human-friendly MCP test client. Usage: ue-wingman.py [key=value ...] - ue-wingman.py (reads JSON with "command" from stdin) + +Values starting with '[' or '{' are parsed as JSON. """ import sys @@ -18,33 +19,19 @@ TIMEOUT = 120 def main(): args = sys.argv[1:] if not args: - # No arguments: read a complete JSON object from stdin. - # The JSON must already contain a "command" key. - decoder = json.JSONDecoder() - raw = "" - msg = None - for line in sys.stdin: - raw += line - stripped = raw.strip() + print("Usage: ue-wingman.py [key=value ...]") + sys.exit(1) + + msg = {"command": args[0]} + for arg in args[1:]: + key, _, value = arg.partition("=") + if value and value[0] in ('[', '{'): try: - msg, _ = decoder.raw_decode(stripped) - break + value = json.loads(value) except json.JSONDecodeError as e: - if e.pos < len(stripped): - print(f"Malformed JSON: {e.msg}") - 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]} - for arg in args[1:]: - key, _, value = arg.partition("=") - msg[key] = value + print(f"Bad JSON in {key}: {e.msg}") + sys.exit(1) + msg[key] = value sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(TIMEOUT)