Precompute broad type categories in WingTypes.

Move the broad-category classification logic out of TypeName_Search and
into WingTypes, store the result in the type registry, and update
TypeName_Search to print the cached category.
This commit is contained in:
2026-04-06 02:56:21 -04:00
parent 5206700067
commit 7aac8f194a
4 changed files with 39 additions and 45 deletions

View File

@@ -1,13 +0,0 @@
# Bugs in UE Wingman
- Partial TCP writes are not handled in [`Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp`](/home/jyelon/integration/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp#L441). The server calls `Socket->Send(...)` once and ignores short writes, so large responses can be truncated and the client can block forever waiting for the terminating null byte.
- Unsigned numeric properties are validated incorrectly in [`Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp`](/home/jyelon/integration/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp#L85) and [`Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp`](/home/jyelon/integration/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp#L125). The code always reads back through `GetSignedIntPropertyValue`, which is the wrong check for unsigned properties and can reject valid values.
- UTF-8 decoding is done per recv chunk instead of on complete messages in [`Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp`](/home/jyelon/integration/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp#L405). If a multibyte code point is split across TCP packets, the request can be corrupted before JSON parsing.
- Request execution is serialized to one queued message per editor tick in [`Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp`](/home/jyelon/integration/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp#L227). Any slow handler blocks every connected client behind it because each client thread waits synchronously for its response.
- Shutdown can hang if a handler is already in flight in [`Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp`](/home/jyelon/integration/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp#L188). Pending queued work is drained, but an active game-thread handler has no cancellation path, and client threads still wait on its unresolved promise.
- The shared output buffer is fixed at 65536 characters in [`Plugins/UEWingman/Source/UEWingman/Public/WingHandler.h`](/home/jyelon/integration/Plugins/UEWingman/Source/UEWingman/Public/WingHandler.h#L75) and instantiated in [`Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp`](/home/jyelon/integration/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp#L502). Large dumps and manual output can be truncated with no warning.

View File

@@ -43,23 +43,6 @@ public:
return Info.IsUserDefined && Info.PinSubCategoryObject.StartsWith(TEXT("/Game/"));
}
static FString BroadCategory(const UWingTypes::Info& Info)
{
if (Info.PinSubCategoryObject.IsEmpty()) return TEXT("Primitive");
if (Info.PinCategory == UEdGraphSchema_K2::PC_Enum) return TEXT("Enum");
if (Info.PinCategory == UEdGraphSchema_K2::PC_Struct) return TEXT("Struct");
if (Info.PinCategory == UEdGraphSchema_K2::PC_Interface) return TEXT("Interface");
if (Info.NativeParent)
{
if (Info.NativeParent->IsChildOf(UUserWidget::StaticClass())) return TEXT("Widget");
if (Info.NativeParent->IsChildOf(UActorComponent::StaticClass())) return TEXT("ActorComponent");
if (Info.NativeParent->IsChildOf(APawn::StaticClass())) return TEXT("Pawn");
if (Info.NativeParent->IsChildOf(AActor::StaticClass())) return TEXT("Actor");
if (Info.NativeParent->IsChildOf(UDataAsset::StaticClass())) return TEXT("DataAsset");
}
return TEXT("Object");
}
static int Importance(const UWingTypes::Info& Info)
{
if (IsProjectDefined(Info)) return 1;
@@ -94,9 +77,9 @@ public:
{
const UWingTypes::Info& Info = *Matches[i];
if (IsProjectDefined(Info))
WingOut::Stdout.Printf(TEXT("%s (%s, User-Defined)\n"), *Info.Short, *BroadCategory(Info));
WingOut::Stdout.Printf(TEXT("%s (%s, User-Defined)\n"), *Info.Short, *Info.BroadCategory.ToString());
else
WingOut::Stdout.Printf(TEXT("%s (%s)\n"), *Info.Short, *BroadCategory(Info));
WingOut::Stdout.Printf(TEXT("%s (%s)\n"), *Info.Short, *Info.BroadCategory.ToString());
}
if (Count >= Limit)
{

View File

@@ -12,6 +12,11 @@
#include "AssetRegistry/AssetRegistryModule.h"
#include "Blueprint/BlueprintSupport.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "GameFramework/Actor.h"
#include "Components/ActorComponent.h"
#include "Blueprint/UserWidget.h"
#include "GameFramework/Pawn.h"
#include "Engine/DataAsset.h"
static const FName NAME_TypeArray(TEXT("Array"));
@@ -137,6 +142,22 @@ const UClass *UWingTypes::FindNativeParent(const UClass *Obj)
return Native;
}
FName UWingTypes::BroadCategory(const UClass* Class)
{
// The nullptr case can really only happen if metadata is missing.
//
if (Class == nullptr) return("Unknown");
// Classify the object type.
//
if (Class->IsChildOf(UUserWidget::StaticClass())) return TEXT("Widget");
if (Class->IsChildOf(UActorComponent::StaticClass())) return TEXT("ActorComponent");
if (Class->IsChildOf(APawn::StaticClass())) return TEXT("Pawn");
if (Class->IsChildOf(AActor::StaticClass())) return TEXT("Actor");
if (Class->IsChildOf(UDataAsset::StaticClass())) return TEXT("DataAsset");
return TEXT("Object");
}
// ---------------------------------------------------------------------------
// Choose Short Name
// ---------------------------------------------------------------------------
@@ -149,7 +170,7 @@ void UWingTypes::ReserveShortName(FName Name, FName PinCategory, FName PinSubCat
Dummy.PinCategory = PinCategory;
Dummy.PinSubCategory = PinSubCategory;
Dummy.PinSubCategoryObject.Empty();
Dummy.NativeParent = nullptr;
Dummy.BroadCategory = TEXT("Primitive");
Dummy.IsUserDefined = false;
ShortToInfo.Add(NameStr.ToLower(), MoveTemp(Dummy));
}
@@ -166,7 +187,7 @@ FString UWingTypes::GetShortName(const FString &Path)
return FString();
}
FString UWingTypes::NewShortName(const FString &Path, FName PinCategory, const UClass *NativeParent, bool IsUserDefined)
FString UWingTypes::NewShortName(const FString &Path, FName PinCategory, FName BroadCategory, bool IsUserDefined)
{
// Verify that the path is not already associated.
check(!PathToShort.Find(Path));
@@ -187,7 +208,7 @@ FString UWingTypes::NewShortName(const FString &Path, FName PinCategory, const U
Info TypeInfo;
TypeInfo.PinCategory = PinCategory;
TypeInfo.PinSubCategoryObject = Path;
TypeInfo.NativeParent = NativeParent;
TypeInfo.BroadCategory = BroadCategory;
TypeInfo.IsUserDefined = IsUserDefined;
// Check if the proposed name is available.
@@ -231,12 +252,14 @@ FString UWingTypes::ChooseShortName(const UObject* Obj)
if (Cast<UEnum>(Obj))
{
return NewShortName(Path, UEdGraphSchema_K2::PC_Enum, nullptr, Cast<UUserDefinedEnum>(Obj) != nullptr);
return NewShortName(Path, UEdGraphSchema_K2::PC_Enum, TEXT("Enum"),
Cast<UUserDefinedEnum>(Obj) != nullptr);
}
if (Cast<UScriptStruct>(Obj))
{
return NewShortName(Path, UEdGraphSchema_K2::PC_Struct, nullptr, Cast<UUserDefinedStruct>(Obj) != nullptr);
return NewShortName(Path, UEdGraphSchema_K2::PC_Struct, TEXT("Struct"),
Cast<UUserDefinedStruct>(Obj) != nullptr);
}
if (const UClass* Class = Cast<UClass>(Obj))
@@ -244,7 +267,7 @@ FString UWingTypes::ChooseShortName(const UObject* Obj)
bool IsInterface = Class->IsChildOf(UInterface::StaticClass());
bool IsUserDefined = (Class->ClassGeneratedBy != nullptr);
FName PinCategory = IsInterface ? UEdGraphSchema_K2::PC_Interface : UEdGraphSchema_K2::PC_Object;
return NewShortName(Path, PinCategory, FindNativeParent(Class), IsUserDefined);
return NewShortName(Path, PinCategory, BroadCategory(Class), IsUserDefined);
}
return FString();
@@ -261,7 +284,7 @@ void UWingTypes::ChooseShortName(const FAssetData &Data)
{
FString Path = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName);
if (!GetShortName(Path).IsEmpty()) return;
NewShortName(Path, UEdGraphSchema_K2::PC_Struct, nullptr, true);
NewShortName(Path, UEdGraphSchema_K2::PC_Struct, TEXT("Struct"), true);
return;
}
@@ -270,7 +293,7 @@ void UWingTypes::ChooseShortName(const FAssetData &Data)
{
FString Path = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName);
if (!GetShortName(Path).IsEmpty()) return;
NewShortName(Path, UEdGraphSchema_K2::PC_Enum, nullptr, true);
NewShortName(Path, UEdGraphSchema_K2::PC_Enum, TEXT("Enum"), true);
return;
}
@@ -294,12 +317,12 @@ void UWingTypes::ChooseShortName(const FAssetData &Data)
if (BPType == TEXT("BPTYPE_Interface"))
{
check(NativeParent->IsChildOf(UInterface::StaticClass()));
NewShortName(Path, UEdGraphSchema_K2::PC_Interface, NativeParent, true);
NewShortName(Path, UEdGraphSchema_K2::PC_Interface, TEXT("Interface"), true);
return;
}
else if (BPType == TEXT("BPTYPE_Normal") || BPType == TEXT("BPTYPE_Const"))
{
NewShortName(Path, UEdGraphSchema_K2::PC_Object, NativeParent, true);
NewShortName(Path, UEdGraphSchema_K2::PC_Object, BroadCategory(NativeParent), true);
return;
}
}
@@ -776,4 +799,3 @@ void UWingTypes::Deinitialize()
}
Super::Deinitialize();
}

View File

@@ -30,7 +30,7 @@ public:
FName PinSubCategory;
FString PinSubCategoryObject;
// The following are only set for interfaces and Objects.
const UClass *NativeParent = nullptr;
FName BroadCategory;
bool IsUserDefined = false;
};
@@ -68,6 +68,8 @@ private:
// Choose Short Name
// ---------------------------------------------------------------------------
static FName BroadCategory(const UClass* Class);
// Reserve the short name for a primitive type.
void ReserveShortName(FName Name, FName PinCategory, FName PinSubCategory);
void ReserveShortName(FName Name);
@@ -77,7 +79,7 @@ private:
// Core version: choose a short name for a path that doesn't already
// have a short name. Records all the associated information.
FString NewShortName(const FString &Path, FName PinCategory, const UClass *NativeParent, bool IsUserDefined);
FString NewShortName(const FString &Path, FName PinCategory, FName BroadCategory, bool IsUserDefined);
// Choose a short name for an already-loaded UObject.
FString ChooseShortName(const UObject* Obj);