Notification changes in UE Wingman

This commit is contained in:
2026-03-18 20:08:50 -04:00
parent 230f104fda
commit 809de505b6
16 changed files with 123 additions and 96 deletions

View File

@@ -94,8 +94,8 @@ public:
UEdGraphNode* Node = F.Node(Entry.Node).Cast<UEdGraphNode>(); UEdGraphNode* Node = F.Node(Entry.Node).Cast<UEdGraphNode>();
if (!Node) return; if (!Node) return;
TArray<WingProperty> All = WingProperty::GetAll(Node, CPF_Edit); TArray<FWingProperty> All = FWingProperty::GetAll(Node, CPF_Edit);
WingProperty P = WingProperty::FindOneExactMatch(All, Entry.Name); FWingProperty P = FWingProperty::FindOneExactMatch(All, Entry.Name);
if (!P) return; if (!P) return;
UWingServer::AddTouchedObject(Node); UWingServer::AddTouchedObject(Node);

View File

@@ -43,17 +43,17 @@ public:
WingFetcher F; WingFetcher F;
UObject* Template = F.Walk(Object).Cast<UObject>(); UObject* Template = F.Walk(Object).Cast<UObject>();
if (!Template) return; if (!Template) return;
TArray<WingProperty> AllProps = WingProperty::GetAll(Template, CPF_Edit); TArray<FWingProperty> AllProps = FWingProperty::GetAll(Template, CPF_Edit);
TArray<WingProperty> Props = WingProperty::FindAllSubstring(AllProps, Query); TArray<FWingProperty> Props = FWingProperty::FindAllSubstring(AllProps, Query);
if (Local) if (Local)
{ {
UClass* ObjClass = Template->GetClass(); UClass* ObjClass = Template->GetClass();
Props.RemoveAll([ObjClass](const WingProperty& P) { return P->GetOwnerStruct() != ObjClass; }); Props.RemoveAll([ObjClass](const FWingProperty& P) { return P->GetOwnerStruct() != ObjClass; });
} }
// Group properties by category. // Group properties by category.
TMap<FString, TArray<WingProperty>> ByCategory; TMap<FString, TArray<FWingProperty>> ByCategory;
for (WingProperty& P : Props) for (FWingProperty& P : Props)
{ {
FString Category = P->HasMetaData(TEXT("Category")) ? P->GetMetaData(TEXT("Category")) : FString(); FString Category = P->HasMetaData(TEXT("Category")) ? P->GetMetaData(TEXT("Category")) : FString();
ByCategory.FindOrAdd(Category).Add(P); ByCategory.FindOrAdd(Category).Add(P);
@@ -75,7 +75,7 @@ public:
else else
UWingServer::Printf(TEXT("\n%s:\n"), *Category); UWingServer::Printf(TEXT("\n%s:\n"), *Category);
for (WingProperty& P : ByCategory[Category]) for (FWingProperty& P : ByCategory[Category])
{ {
FString PropName = WingUtils::FormatName(P.Prop); FString PropName = WingUtils::FormatName(P.Prop);
FString ValueStr = P.GetText(); FString ValueStr = P.GetText();

View File

@@ -36,8 +36,8 @@ public:
UObject* Obj = F.Walk(Object).Cast<UObject>(); UObject* Obj = F.Walk(Object).Cast<UObject>();
if (!Obj) return; if (!Obj) return;
TArray<WingProperty> All = WingProperty::GetAll(Obj, CPF_Edit); TArray<FWingProperty> All = FWingProperty::GetAll(Obj, CPF_Edit);
WingProperty P = WingProperty::FindOneExactMatch(All, Property); FWingProperty P = FWingProperty::FindOneExactMatch(All, Property);
if (!P) return; if (!P) return;
UWingServer::Print(P.GetText()); UWingServer::Print(P.GetText());

View File

@@ -44,11 +44,11 @@ public:
} }
// Validation pass — resolve all properties and values before modifying anything. // Validation pass — resolve all properties and values before modifying anything.
TArray<WingProperty> All = WingProperty::GetAll(Obj, CPF_Edit); TArray<FWingProperty> All = FWingProperty::GetAll(Obj, CPF_Edit);
TArray<TPair<WingProperty, FString>> Resolved; TArray<TPair<FWingProperty, FString>> Resolved;
for (const auto& Pair : Properties.Json->Values) for (const auto& Pair : Properties.Json->Values)
{ {
WingProperty P = WingProperty::FindOneExactMatch(All, Pair.Key); FWingProperty P = FWingProperty::FindOneExactMatch(All, Pair.Key);
if (!P) return; if (!P) return;
FString ValueStr; FString ValueStr;

View File

@@ -23,7 +23,7 @@ FWingBlueprintVar::FWingBlueprintVar(UBlueprint* BP, const FString& VarName)
UObject* CDO = BP->GeneratedClass->GetDefaultObject(); UObject* CDO = BP->GeneratedClass->GetDefaultObject();
FProperty* Prop = BP->GeneratedClass->FindPropertyByName(VarFName); FProperty* Prop = BP->GeneratedClass->FindPropertyByName(VarFName);
if (CDO && Prop) if (CDO && Prop)
DefaultValueProp = WingProperty(Prop, CDO); DefaultValueProp = FWingProperty(Prop, CDO);
} }
} }
@@ -31,8 +31,8 @@ void FWingBlueprintVar::Dump()
{ {
LoadFlags(); LoadFlags();
LoadDefault(); LoadDefault();
TArray<WingProperty> Props = MergedProperties(); TArray<FWingProperty> Props = MergedProperties();
for (WingProperty& P : Props) for (FWingProperty& P : Props)
{ {
UWingServer::Printf(TEXT(" %s %s = %s\n"), UWingServer::Printf(TEXT(" %s %s = %s\n"),
*UWingTypes::TypeToText(P.Prop), *UWingTypes::TypeToText(P.Prop),
@@ -56,7 +56,7 @@ bool FWingBlueprintVar::ApplyJson(const FJsonObject* Json)
LoadFlags(); LoadFlags();
TArray<WingProperty> Props = MergedProperties(); TArray<FWingProperty> Props = MergedProperties();
if (!WingJson::PopulateFromJson(Props, Json, true)) if (!WingJson::PopulateFromJson(Props, Json, true))
return false; return false;
@@ -131,23 +131,23 @@ bool FWingBlueprintVar::SaveDefault()
return true; return true;
} }
TArray<WingProperty> FWingBlueprintVar::MergedProperties() TArray<FWingProperty> FWingBlueprintVar::MergedProperties()
{ {
TArray<WingProperty> Props = WingProperty::GetAll( TArray<FWingProperty> Props = FWingProperty::GetAll(
FBPVariableDescription::StaticStruct(), Desc, CPF_Edit); FBPVariableDescription::StaticStruct(), Desc, CPF_Edit);
WingProperty::Remove(Props, TEXT("PropertyFlags")); FWingProperty::Remove(Props, TEXT("PropertyFlags"));
WingProperty::Remove(Props, TEXT("MetaDataArray")); FWingProperty::Remove(Props, TEXT("MetaDataArray"));
WingProperty::Remove(Props, TEXT("VarName")); FWingProperty::Remove(Props, TEXT("VarName"));
WingProperty::Remove(Props, TEXT("VarGuid")); FWingProperty::Remove(Props, TEXT("VarGuid"));
WingProperty::Remove(Props, TEXT("DefaultValue")); FWingProperty::Remove(Props, TEXT("DefaultValue"));
Props.Append(WingProperty::GetAll( Props.Append(FWingProperty::GetAll(
FWingBlueprintVar::StaticStruct(), this, (EPropertyFlags)0)); FWingBlueprintVar::StaticStruct(), this, (EPropertyFlags)0));
// Remove DefaultValue if we don't have a CDO property to back it. // Remove DefaultValue if we don't have a CDO property to back it.
if (!DefaultValueProp) if (!DefaultValueProp)
WingProperty::Remove(Props, TEXT("DefaultValue")); FWingProperty::Remove(Props, TEXT("DefaultValue"));
return Props; return Props;
} }

View File

@@ -27,7 +27,8 @@ WingFetcher::WalkFunc WingFetcher::GetWalker(const FString& Step)
void WingFetcher::SetObj(UObject* InObj) void WingFetcher::SetObj(UObject* InObj)
{ {
UWingServer::AddTouchedObject(InObj); if (!bSkipNotify)
UWingServer::AddTouchedObject(InObj);
Obj = InObj; Obj = InObj;
ResultPin = nullptr; ResultPin = nullptr;
} }

View File

@@ -6,7 +6,7 @@
#include "Dom/JsonValue.h" #include "Dom/JsonValue.h"
bool WingJson::PopulateFromJson(WingProperty& P, const FJsonObject* Json, bool AllOptional) bool WingJson::PopulateFromJson(FWingProperty& P, const FJsonObject* Json, bool AllOptional)
{ {
FString JsonKey = P.Prop->GetName(); FString JsonKey = P.Prop->GetName();
bool bOptional = AllOptional || P.Prop->HasMetaData(TEXT("Optional")); bool bOptional = AllOptional || P.Prop->HasMetaData(TEXT("Optional"));
@@ -85,13 +85,13 @@ bool WingJson::PopulateFromJson(WingProperty& P, const FJsonObject* Json, bool A
} }
bool WingJson::PopulateFromJson( bool WingJson::PopulateFromJson(
TArray<WingProperty>& Props, const FJsonObject* Json, bool AllOptional) TArray<FWingProperty>& Props, const FJsonObject* Json, bool AllOptional)
{ {
bool Ok = true; bool Ok = true;
// 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<FString> KnownKeys; TSet<FString> KnownKeys;
for (const WingProperty& P : Props) for (const FWingProperty& P : Props)
KnownKeys.Add(P.Prop->GetName()); KnownKeys.Add(P.Prop->GetName());
// Check for unknown fields in the JSON // Check for unknown fields in the JSON
@@ -105,7 +105,7 @@ bool WingJson::PopulateFromJson(
} }
// Populate each property from JSON // Populate each property from JSON
for (WingProperty& P : Props) for (FWingProperty& P : Props)
{ {
if (!PopulateFromJson(P, Json, AllOptional)) Ok = false; if (!PopulateFromJson(P, Json, AllOptional)) Ok = false;
} }
@@ -115,7 +115,7 @@ bool WingJson::PopulateFromJson(
bool WingJson::PopulateFromJson( bool WingJson::PopulateFromJson(
UStruct* StructType, void* Container, const FJsonObject* Json) UStruct* StructType, void* Container, const FJsonObject* Json)
{ {
TArray<WingProperty> Props = WingProperty::GetAll(StructType, Container, (EPropertyFlags)0); TArray<FWingProperty> Props = FWingProperty::GetAll(StructType, Container, (EPropertyFlags)0);
return PopulateFromJson(Props, Json); return PopulateFromJson(Props, Json);
} }

View File

@@ -3,29 +3,29 @@
#include "EdGraph/EdGraph.h" #include "EdGraph/EdGraph.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "Materials/Material.h" #include "Materials/Material.h"
#include "Materials/MaterialExpression.h"
#include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/BlueprintEditorUtils.h"
#include "MaterialEditingLibrary.h" #include "MaterialEditingLibrary.h"
void WingNotifier::AddTouchedObject(UObject* Obj) void FWingNotifier::AddTouchedObject(UObject* Obj)
{ {
if (!Obj) return; if (!Obj) return;
bool bAlreadyInSet = false; bool bAlreadyInSet = false;
TouchedSet.Add(Obj, &bAlreadyInSet); TouchedSet.Add(Obj, &bAlreadyInSet);
if (bAlreadyInSet) return; if (bAlreadyInSet) return;
TouchedArray.Add(Obj); TouchedArray.Add(Obj);
Obj->PreEditChange(nullptr);
} }
void WingNotifier::SendNotifications() void FWingNotifier::SendNotifications()
{ {
TSet<UEdGraphNode*> Nodes; TSet<UEdGraphNode*> Nodes;
TSet<UEdGraph*> Graphs; TSet<UEdGraph*> Graphs;
TSet<UMaterial*> Materials; TSet<UMaterial*> Materials;
TSet<UMaterialExpression*> MaterialExpressions;
TSet<UBlueprint*> Blueprints; TSet<UBlueprint*> Blueprints;
for (int32 i = TouchedArray.Num() - 1; i >= 0; --i) for (int32 i = TouchedArray.Num() - 1; i >= 0; --i)
{ {
UObject* Obj = TouchedArray[i]; UObject* Obj = TouchedArray[i];
Obj->PostEditChange();
Obj->MarkPackageDirty(); Obj->MarkPackageDirty();
if (UEdGraphNode* Node = ::Cast<UEdGraphNode>(Obj)) if (UEdGraphNode* Node = ::Cast<UEdGraphNode>(Obj))
@@ -37,12 +37,21 @@ void WingNotifier::SendNotifications()
if (UBlueprint* BP = ::Cast<UBlueprint>(Obj)) if (UBlueprint* BP = ::Cast<UBlueprint>(Obj))
Blueprints.Add(BP); Blueprints.Add(BP);
if (UMaterialExpression* Expr = ::Cast<UMaterialExpression>(Obj))
MaterialExpressions.Add(Expr);
if (UMaterial* Mat = ::Cast<UMaterial>(Obj))
Materials.Add(Mat);
if (UMaterialInterface* MatIface = ::Cast<UMaterialInterface>(Obj)) if (UMaterialInterface* MatIface = ::Cast<UMaterialInterface>(Obj))
if (UMaterial* BaseMat = MatIface->GetMaterial()) if (UMaterial* BaseMat = MatIface->GetMaterial())
Materials.Add(BaseMat); Materials.Add(BaseMat);
} }
for (UEdGraphNode* Node : Nodes)
Node->ReconstructNode(); for (UMaterialExpression* Expr : MaterialExpressions)
Expr->RefreshNode(true);
// for (UEdGraphNode* Node : Nodes)
// Node->ReconstructNode();
for (UEdGraph* Graph : Graphs) for (UEdGraph* Graph : Graphs)
Graph->NotifyGraphChanged(); Graph->NotifyGraphChanged();
for (UMaterial *Material : Materials) for (UMaterial *Material : Materials)

View File

@@ -3,7 +3,6 @@
#include "WingServer.h" #include "WingServer.h"
#include "WingTypes.h" #include "WingTypes.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "Materials/MaterialExpression.h"
#include "MaterialGraph/MaterialGraphNode.h" #include "MaterialGraph/MaterialGraphNode.h"
#include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraphPin.h"
#include "UObject/EnumProperty.h" #include "UObject/EnumProperty.h"
@@ -14,10 +13,13 @@ static bool IsPinTypeProperty(FProperty* Prop)
return StructProp && StructProp->Struct == FEdGraphPinType::StaticStruct(); return StructProp && StructProp->Struct == FEdGraphPinType::StaticStruct();
} }
WingProperty::WingProperty(FProperty* InProp, void* InContainer) FWingProperty::FWingProperty(FProperty* InProp, void* InContainer)
: Prop(InProp), Container(InContainer) {} : Prop(InProp), Container(InContainer) {}
FString WingProperty::GetText() const FWingProperty::FWingProperty(FProperty* InProp, UObject* InContainer)
: Prop(InProp), Container(static_cast<void*>(InContainer)) {}
FString FWingProperty::GetText() const
{ {
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container); void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
if (IsPinTypeProperty(Prop)) if (IsPinTypeProperty(Prop))
@@ -27,7 +29,7 @@ FString WingProperty::GetText() const
return Result; return Result;
} }
bool WingProperty::TryParseEnum(UEnum* Enum, const FString& Text, int64 &OutValue) bool FWingProperty::TryParseEnum(UEnum* Enum, const FString& Text, int64 &OutValue)
{ {
int Index = Enum->GetIndexByNameString(Text); int Index = Enum->GetIndexByNameString(Text);
if (Index == INDEX_NONE) if (Index == INDEX_NONE)
@@ -52,10 +54,14 @@ bool WingProperty::TryParseEnum(UEnum* Enum, const FString& Text, int64 &OutValu
} }
} }
bool WingProperty::TrySetText(const FString &Value) bool FWingProperty::SetText(const FString &Value)
{ {
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container); void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
// Notify that we're modifying the containing object.
if (Prop->GetOwnerClass())
UWingServer::AddTouchedObject(static_cast<UObject*>(Container));
// Pin types get parsed by UWingTypes. // Pin types get parsed by UWingTypes.
if (IsPinTypeProperty(Prop)) if (IsPinTypeProperty(Prop))
return UWingTypes::TextToType(Value, *static_cast<FEdGraphPinType*>(ValuePtr)); return UWingTypes::TextToType(Value, *static_cast<FEdGraphPinType*>(ValuePtr));
@@ -92,20 +98,7 @@ bool WingProperty::TrySetText(const FString &Value)
return true; return true;
} }
bool WingProperty::SetText(const FString& Value) void FWingProperty::Collect(UStruct* StructType, void* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags)
{
if (!TrySetText(Value)) return false;
if (Prop->GetOwnerClass()->IsChildOf(UMaterialExpression::StaticClass()))
{
UMaterialExpression* Expr = static_cast<UMaterialExpression*>(Container);
Expr->ForcePropertyValueChanged(Prop);
}
return true;
}
void WingProperty::Collect(UStruct* StructType, void* Container, TArray<WingProperty> &Props, EPropertyFlags Flags)
{ {
for (TFieldIterator<FProperty> It(StructType); It; ++It) for (TFieldIterator<FProperty> It(StructType); It; ++It)
{ {
@@ -114,15 +107,24 @@ void WingProperty::Collect(UStruct* StructType, void* Container, TArray<WingProp
} }
} }
void WingProperty::Remove(TArray<WingProperty>& Props, const FString& Name) void FWingProperty::Collect(UObject* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags)
{ {
Props.RemoveAll([&](const WingProperty& P) { return P.Prop->GetName() == Name; }); for (TFieldIterator<FProperty> It(Container->GetClass()); It; ++It)
{
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
Props.Emplace(*It, Container);
}
} }
TArray<WingProperty> WingProperty::GetAll(UObject* Obj, EPropertyFlags Flags) void FWingProperty::Remove(TArray<FWingProperty>& Props, const FString& Name)
{
Props.RemoveAll([&](const FWingProperty& P) { return P.Prop->GetName() == Name; });
}
TArray<FWingProperty> FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
{ {
if (!Obj) return {}; if (!Obj) return {};
TArray<WingProperty> Result; TArray<FWingProperty> Result;
// Blueprints don't have editable properties. So // Blueprints don't have editable properties. So
// instead, we fetch properties from the generated CDO, // instead, we fetch properties from the generated CDO,
@@ -138,7 +140,7 @@ TArray<WingProperty> WingProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
Obj = BP->GeneratedClass->GetDefaultObject(); Obj = BP->GeneratedClass->GetDefaultObject();
} }
Collect(Obj->GetClass(), Obj, Result, Flags); Collect(Obj, Result, Flags);
// If it's a Material Graph node, also collect properties from // If it's a Material Graph node, also collect properties from
// the associated material expression. // the associated material expression.
@@ -147,24 +149,24 @@ TArray<WingProperty> WingProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
{ {
if (UMaterialExpression* Expr = MatNode->MaterialExpression) if (UMaterialExpression* Expr = MatNode->MaterialExpression)
{ {
Collect(Expr->GetClass(), Expr, Result, Flags); Collect(Expr, Result, Flags);
} }
} }
return Result; return Result;
} }
TArray<WingProperty> WingProperty::GetAll(UStruct* StructType, void* Container, EPropertyFlags Flags) TArray<FWingProperty> FWingProperty::GetAll(UStruct* StructType, void* Container, EPropertyFlags Flags)
{ {
TArray<WingProperty> Result; TArray<FWingProperty> Result;
Collect(StructType, Container, Result, Flags); Collect(StructType, Container, Result, Flags);
return Result; return Result;
} }
TArray<WingProperty> WingProperty::FindAllSubstring(const TArray<WingProperty>& Props, const FString& Substring) TArray<FWingProperty> FWingProperty::FindAllSubstring(const TArray<FWingProperty>& Props, const FString& Substring)
{ {
if (Substring.IsEmpty()) return Props; if (Substring.IsEmpty()) return Props;
TArray<WingProperty> Result; TArray<FWingProperty> Result;
for (const WingProperty& P : Props) for (const FWingProperty& P : Props)
{ {
if (WingUtils::FormatName(P.Prop).Contains(Substring, ESearchCase::IgnoreCase)) if (WingUtils::FormatName(P.Prop).Contains(Substring, ESearchCase::IgnoreCase))
Result.Add(P); Result.Add(P);
@@ -172,10 +174,10 @@ TArray<WingProperty> WingProperty::FindAllSubstring(const TArray<WingProperty>&
return Result; return Result;
} }
WingProperty WingProperty::FindOneExactMatch(const TArray<WingProperty>& Props, const FString& Name) FWingProperty FWingProperty::FindOneExactMatch(const TArray<FWingProperty>& Props, const FString& Name)
{ {
TArray<WingProperty> Matches; TArray<FWingProperty> Matches;
for (const WingProperty& P : Props) for (const FWingProperty& P : Props)
{ {
if (WingUtils::Identifies(Name, P.Prop)) if (WingUtils::Identifies(Name, P.Prop))
Matches.Add(P); Matches.Add(P);
@@ -183,12 +185,12 @@ WingProperty WingProperty::FindOneExactMatch(const TArray<WingProperty>& Props,
if (Matches.Num() == 0) if (Matches.Num() == 0)
{ {
UWingServer::Printf(TEXT("ERROR: Property '%s' not found\n"), *Name); UWingServer::Printf(TEXT("ERROR: Property '%s' not found\n"), *Name);
return WingProperty(); return FWingProperty();
} }
if (Matches.Num() > 1) if (Matches.Num() > 1)
{ {
UWingServer::Printf(TEXT("ERROR: Ambiguous property '%s'\n"), *Name); UWingServer::Printf(TEXT("ERROR: Ambiguous property '%s'\n"), *Name);
return WingProperty(); return FWingProperty();
} }
return Matches[0]; return Matches[0];
} }

View File

@@ -304,7 +304,7 @@ void UWingServer::TryCallHandler(const FString &Line)
Request->RemoveField(TEXT("command")); Request->RemoveField(TEXT("command"));
// Find the handler UClass for the specified command. // Find the handler UClass for the specified command.
UClass** HandlerClass = WingHandlerRegistry.Find(Command); TObjectPtr<UClass>* HandlerClass = WingHandlerRegistry.Find(Command);
if (!HandlerClass) if (!HandlerClass)
{ {
UWingServer::Printf(TEXT("Unknown command: %s"), *Command); UWingServer::Printf(TEXT("Unknown command: %s"), *Command);

View File

@@ -15,7 +15,7 @@ struct FWingBlueprintVar
GENERATED_BODY() GENERATED_BODY()
FBPVariableDescription* Desc = nullptr; FBPVariableDescription* Desc = nullptr;
WingProperty DefaultValueProp; FWingProperty DefaultValueProp;
FWingBlueprintVar() = default; FWingBlueprintVar() = default;
FWingBlueprintVar(UBlueprint* BP, const FString& VarName); FWingBlueprintVar(UBlueprint* BP, const FString& VarName);
@@ -54,5 +54,5 @@ private:
void LoadDefault(); void LoadDefault();
void SaveFlags(); void SaveFlags();
bool SaveDefault(); bool SaveDefault();
TArray<WingProperty> MergedProperties(); TArray<FWingProperty> MergedProperties();
}; };

View File

@@ -105,6 +105,12 @@ public:
return static_cast<EditorType*>(Editor); return static_cast<EditorType*>(Editor);
} }
// Calling SkipNotify disables change notifications
// for objects visited by this fetcher. Useful for
// read-only operations that don't modify anything.
//
WingFetcher& SkipNotify() { bSkipNotify = true; return *this; }
// Initialize empty. You need to call Asset, or walk // Initialize empty. You need to call Asset, or walk
// a path that starts with an asset. // a path that starts with an asset.
// //
@@ -128,6 +134,7 @@ private:
// True if an error has occurred. // True if an error has occurred.
bool bError = false; bool bError = false;
bool bSkipNotify = false;
// Internal methods. // Internal methods.
using WalkFunc = WingFetcher& (WingFetcher::*)(const FString&); using WalkFunc = WingFetcher& (WingFetcher::*)(const FString&);

View File

@@ -10,8 +10,8 @@
class WingJson class WingJson
{ {
public: public:
static bool PopulateFromJson(WingProperty& Prop, const FJsonObject* Json, bool AllOptional = false); static bool PopulateFromJson(FWingProperty& Prop, const FJsonObject* Json, bool AllOptional = false);
static bool PopulateFromJson(TArray<WingProperty>& Props, const FJsonObject* Json, bool AllOptional = false); static bool PopulateFromJson(TArray<FWingProperty>& Props, const FJsonObject* Json, bool AllOptional = false);
static bool PopulateFromJson(UStruct* StructType, void* Container, const TSharedPtr<FJsonValue>& JsonValue); static bool PopulateFromJson(UStruct* StructType, void* Container, const TSharedPtr<FJsonValue>& JsonValue);
static bool PopulateFromJson(UStruct* StructType, void* Container, const FJsonObject* Json); static bool PopulateFromJson(UStruct* StructType, void* Container, const FJsonObject* Json);
}; };

View File

@@ -1,18 +1,24 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "WingNotifier.generated.h"
// Tracks objects that have been touched during an editing operation. // Tracks objects that have been touched during an editing operation.
// Handles PreEditChange/PostEditChange, ReconstructNode, and other // Handles PreEditChange/PostEditChange, ReconstructNode, and other
// notifications that need to happen after modifications. // notifications that need to happen after modifications.
// //
class WingNotifier USTRUCT()
struct FWingNotifier
{ {
public: GENERATED_BODY()
void AddTouchedObject(UObject* Obj); void AddTouchedObject(UObject* Obj);
void SendNotifications(); void SendNotifications();
private: private:
TSet<UObject*> TouchedSet; UPROPERTY()
TArray<UObject*> TouchedArray; TSet<TObjectPtr<UObject>> TouchedSet;
UPROPERTY()
TArray<TObjectPtr<UObject>> TouchedArray;
}; };

View File

@@ -2,17 +2,17 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "WingUtils.h" #include "WingUtils.h"
// A resolved property: the FProperty descriptor plus a pointer to // A resolved property: the FProperty descriptor plus a pointer to
// the value's storage. operator-> forwards to the FProperty. // the value's storage. operator-> forwards to the FProperty.
struct WingProperty struct FWingProperty
{ {
public:
FProperty* Prop = nullptr; FProperty* Prop = nullptr;
void* Container = nullptr; void* Container = nullptr;
WingProperty() = default; FWingProperty() = default;
WingProperty(FProperty* InProp, void* Container); FWingProperty(FProperty* InProp, void* InContainer);
FWingProperty(FProperty* InProp, UObject* InContainer);
FString GetText() const; FString GetText() const;
bool SetText(const FString& Value); bool SetText(const FString& Value);
@@ -20,14 +20,14 @@ public:
explicit operator bool() const { return Prop != nullptr; } explicit operator bool() const { return Prop != nullptr; }
FProperty* operator->() const { return Prop; } FProperty* operator->() const { return Prop; }
static void Remove(TArray<WingProperty>& Props, const FString& Name); static void Remove(TArray<FWingProperty>& Props, const FString& Name);
static TArray<WingProperty> GetAll(UObject* Obj, EPropertyFlags Flags); static TArray<FWingProperty> GetAll(UObject* Obj, EPropertyFlags Flags);
static TArray<WingProperty> GetAll(UStruct* StructType, void* Container, EPropertyFlags Flags); static TArray<FWingProperty> GetAll(UStruct* StructType, void* Container, EPropertyFlags Flags);
static TArray<WingProperty> FindAllSubstring(const TArray<WingProperty>& Props, const FString& Substring); static TArray<FWingProperty> FindAllSubstring(const TArray<FWingProperty>& Props, const FString& Substring);
static WingProperty FindOneExactMatch(const TArray<WingProperty>& Props, const FString& Name); static FWingProperty FindOneExactMatch(const TArray<FWingProperty>& Props, const FString& Name);
private: private:
bool TryParseEnum(UEnum* Enum, const FString& Text, int64 &OutValue); bool TryParseEnum(UEnum* Enum, const FString& Text, int64 &OutValue);
bool TrySetText(const FString &Text); static void Collect(UStruct* StructType, void* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags);
static void Collect(UStruct* StructType, void* Container, TArray<WingProperty> &Props, EPropertyFlags Flags); static void Collect(UObject* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags);
}; };

View File

@@ -69,10 +69,12 @@ private:
static UWingServer* GWingServer; static UWingServer* GWingServer;
// ----- Tool dispatch ----- // ----- Tool dispatch -----
WingNotifier Notifier; UPROPERTY()
FWingNotifier Notifier;
TStringBuilder<16384> HandlerOutput; TStringBuilder<16384> HandlerOutput;
FLogCaptureOutputDevice LogCapture; // installed once at startup, enabled per-request FLogCaptureOutputDevice LogCapture; // installed once at startup, enabled per-request
TMap<FString, UClass*> WingHandlerRegistry; // tool name -> UWingHandler subclass UPROPERTY()
TMap<FString, TObjectPtr<UClass>> WingHandlerRegistry; // tool name -> UWingHandler subclass
void BuildWingHandlerRegistry(); void BuildWingHandlerRegistry();
// Handle a complete JSON line and return the response JSON // Handle a complete JSON line and return the response JSON