Handler registration overhaul

This commit is contained in:
2026-04-01 17:45:33 -04:00
parent 7e53975e3d
commit 483b3b75ff
74 changed files with 362 additions and 255 deletions

View File

@@ -20,7 +20,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_ActorComponent_Add : public UObject, public IWingHandler
class UWing_ActorComponent_Add : public UWingHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_ActorComponent_Remove : public UObject, public IWingHandler
class UWing_ActorComponent_Remove : public UWingHandler
{
GENERATED_BODY()

View File

@@ -17,7 +17,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_ActorComponent_Reparent : public UObject, public IWingHandler
class UWing_ActorComponent_Reparent : public UWingHandler
{
GENERATED_BODY()

View File

@@ -15,7 +15,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Asset_Backup : public UObject, public IWingHandler
class UWing_Asset_Backup : public UWingHandler
{
GENERATED_BODY()

View File

@@ -15,7 +15,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Asset_ContentBrowse : public UObject, public IWingHandler
class UWing_Asset_ContentBrowse : public UWingHandler
{
GENERATED_BODY()

View File

@@ -1,39 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFactories.h"
#include "Asset_Create.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Asset_Create : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Full asset path for the new asset (e.g. '/Game/MyFolder/MyAsset')"))
FString AssetPath;
UPROPERTY(meta=(Description="Factory type name from Asset_SearchTypes (e.g. 'Material', 'Blueprint')"))
FString Factory;
UPROPERTY(meta=(Optional, Description="Factory configuration properties as key-value pairs"))
FWingJsonObject Config;
virtual FString GetDescription() const override
{
return TEXT("Create a new asset using a factory. Use Asset_SearchTypes to find "
"available factory types and their configurable properties.");
}
virtual void Handle() override
{
UWingFactories::CreateAsset(AssetPath, Factory, Config);
}
};

View File

@@ -18,7 +18,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Asset_Delete : public UObject, public IWingHandler
class UWing_Asset_Delete : public UWingHandler
{
GENERATED_BODY()

View File

@@ -14,7 +14,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Asset_FindReferences : public UObject, public IWingHandler
class UWing_Asset_FindReferences : public UWingHandler
{
GENERATED_BODY()

View File

@@ -15,7 +15,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Asset_Rename : public UObject, public IWingHandler
class UWing_Asset_Rename : public UWingHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Asset_Restore : public UObject, public IWingHandler
class UWing_Asset_Restore : public UWingHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Asset_Search : public UObject, public IWingHandler
class UWing_Asset_Search : public UWingHandler
{
GENERATED_BODY()

View File

@@ -1,63 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFactories.h"
#include "Asset_SearchTypes.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Asset_SearchTypes : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Query string, can contain *"))
FString Query;
UPROPERTY(meta=(Optional, Description="Maximum number of results (default 50)"))
int32 MaxResults = 50;
virtual FString GetDescription() const override
{
return TEXT("Search for asset factory types that can be used with Asset_Create. "
"Returns factory names and their configurable properties.");
}
virtual void Handle() override
{
FString ExtQuery = TEXT("*") + Query + TEXT("*");
const TArray<UWingFactories::Info>& All = UWingFactories::AllFactories();
int32 Count = 0;
for (const UWingFactories::Info& Entry : All)
{
if (Count >= MaxResults) break;
if (!Entry.CanCreateNew()) continue;
if (!Entry.Name.MatchesWildcard(ExtQuery, ESearchCase::IgnoreCase)) continue;
UWingServer::Printf(TEXT("%s\n"), *Entry.Name);
for (const FName& Prop : Entry.Config)
{
UWingServer::Printf(TEXT(" %s\n"), *Prop.ToString());
}
Count++;
}
if (Count == 0)
{
UWingServer::Print(TEXT("No matching factory types found.\n"));
}
else if (Count >= MaxResults)
{
UWingServer::Printf(TEXT("WARNING: Reached limit of %d results. You may specify MaxResults.\n"), MaxResults);
}
}
};

View File

@@ -18,7 +18,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_BlueprintGraph_Create : public UObject, public IWingHandler
class UWing_BlueprintGraph_Create : public UWingHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_BlueprintGraph_Delete : public UObject, public IWingHandler
class UWing_BlueprintGraph_Delete : public UWingHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Blueprint_AddInterface : public UObject, public IWingHandler
class UWing_Blueprint_AddInterface : public UWingHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Blueprint_RemoveInterface : public UObject, public IWingHandler
class UWing_Blueprint_RemoveInterface : public UWingHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Blueprint_Compile : public UObject, public IWingHandler
class UWing_Blueprint_Compile : public UWingHandler
{
GENERATED_BODY()

View File

@@ -18,7 +18,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Blueprint_Create : public UObject, public IWingHandler
class UWing_Blueprint_Create : public UWingHandler
{
GENERATED_BODY()

View File

@@ -26,7 +26,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Blueprint_Dump : public UObject, public IWingHandler
class UWing_Blueprint_Dump : public UWingHandler
{
GENERATED_BODY()

View File

@@ -17,7 +17,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Blueprint_Reparent : public UObject, public IWingHandler
class UWing_Blueprint_Reparent : public UWingHandler
{
GENERATED_BODY()

View File

@@ -0,0 +1,51 @@
#pragma once
#include "CoreMinimal.h"
#include "Factories/Factory.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFactories.h"
#include "WingProperty.h"
#include "Create_UsingFactory.generated.h"
UCLASS()
class UWing_Create_UsingFactory : public UWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Full asset path for the new asset (e.g. '/Game/MyFolder/MyAsset')"))
FString AssetPath;
virtual FString GetDescription() const override
{
return TEXT("Create a new asset using a factory.");
}
virtual void Register(UWingServer* Server) override
{
TArray<UClass*> FactoryClasses;
GetDerivedClasses(UFactory::StaticClass(), FactoryClasses);
for (UClass* Class : FactoryClasses)
{
if (Class->HasAnyClassFlags(CLASS_Abstract)) continue;
UFactory* CDO = Class->GetDefaultObject<UFactory>();
if (!CDO->CanCreateNew() || !CDO->ShouldShowInNewMenu()) continue;
TArray<FName> ConfigProps = FWingProperty::GetNames(Class, CPF_Edit);
if (ConfigProps.Num() > 0) continue;
FString CommandName = FString::Printf(TEXT("Create_%s"), *UWingFactories::DeriveFactoryName(Class));
Server->AddHandler(CommandName, GetClass(), Class, EWingHandlerKind::Create);
}
}
virtual void Handle() override
{
UClass* FactoryClass = Cast<UClass>(ConfigurationObject);
UFactory* Factory = NewObject<UFactory>(GetTransientPackage(), FactoryClass);
UWingFactories::CreateAsset(AssetPath, Factory);
}
};

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Editor_ListOpenAssets : public UObject, public IWingHandler
class UWing_Editor_ListOpenAssets : public UWingHandler
{
GENERATED_BODY()

View File

@@ -14,7 +14,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Editor_OpenAsset : public UObject, public IWingHandler
class UWing_Editor_OpenAsset : public UWingHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_EventDispatcher_Create : public UObject, public IWingHandler
class UWing_EventDispatcher_Create : public UWingHandler
{
GENERATED_BODY()

View File

@@ -15,7 +15,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_EventDispatcher_Delete : public UObject, public IWingHandler
class UWing_EventDispatcher_Delete : public UWingHandler
{
GENERATED_BODY()

View File

@@ -14,7 +14,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_GraphNode_ChooseMenu : public UObject, public IWingHandler
class UWing_GraphNode_ChooseMenu : public UWingHandler
{
GENERATED_BODY()

View File

@@ -34,7 +34,7 @@ struct FSpawnNodeEntry
UCLASS()
class UWing_GraphNode_Create : public UObject, public IWingHandler
class UWing_GraphNode_Create : public UWingHandler
{
GENERATED_BODY()

View File

@@ -18,7 +18,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_GraphNode_Delete : public UObject, public IWingHandler
class UWing_GraphNode_Delete : public UWingHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_GraphNode_Dump : public UObject, public IWingHandler
class UWing_GraphNode_Dump : public UWingHandler
{
GENERATED_BODY()

View File

@@ -14,7 +14,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_GraphNode_GetComment : public UObject, public IWingHandler
class UWing_GraphNode_GetComment : public UWingHandler
{
GENERATED_BODY()

View File

@@ -14,7 +14,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_GraphNode_Rename : public UObject, public IWingHandler
class UWing_GraphNode_Rename : public UWingHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_GraphNode_SearchTypes : public UObject, public IWingHandler
class UWing_GraphNode_SearchTypes : public UWingHandler
{
GENERATED_BODY()

View File

@@ -14,7 +14,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_GraphNode_SetComment : public UObject, public IWingHandler
class UWing_GraphNode_SetComment : public UWingHandler
{
GENERATED_BODY()

View File

@@ -33,7 +33,7 @@ struct FSetNodeDefaultEntry
UCLASS()
class UWing_GraphNode_SetDefaults : public UObject, public IWingHandler
class UWing_GraphNode_SetDefaults : public UWingHandler
{
GENERATED_BODY()

View File

@@ -31,7 +31,7 @@ struct FMoveNodeEntry
UCLASS()
class UWing_GraphNode_SetPositions : public UObject, public IWingHandler
class UWing_GraphNode_SetPositions : public UWingHandler
{
GENERATED_BODY()

View File

@@ -15,7 +15,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_GraphNode_ShowMenu : public UObject, public IWingHandler
class UWing_GraphNode_ShowMenu : public UWingHandler
{
GENERATED_BODY()

View File

@@ -31,7 +31,7 @@ struct FConnectPinsEntry
UCLASS()
class UWing_GraphPin_Connect : public UObject, public IWingHandler
class UWing_GraphPin_Connect : public UWingHandler
{
GENERATED_BODY()

View File

@@ -30,7 +30,7 @@ struct FDisconnectPinEntry
UCLASS()
class UWing_GraphPin_Disconnect : public UObject, public IWingHandler
class UWing_GraphPin_Disconnect : public UWingHandler
{
GENERATED_BODY()

View File

@@ -18,7 +18,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Graph_Dump : public UObject, public IWingHandler
class UWing_Graph_Dump : public UWingHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_MaterialInstance_ClearParameter : public UObject, public IWingHandler
class UWing_MaterialInstance_ClearParameter : public UWingHandler
{
GENERATED_BODY()

View File

@@ -18,7 +18,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_MaterialInstance_Create : public UObject, public IWingHandler
class UWing_MaterialInstance_Create : public UWingHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_MaterialInstance_DumpParameters : public UObject, public IWingHandler
class UWing_MaterialInstance_DumpParameters : public UWingHandler
{
GENERATED_BODY()

View File

@@ -17,7 +17,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_MaterialInstance_SetParameter : public UObject, public IWingHandler
class UWing_MaterialInstance_SetParameter : public UWingHandler
{
GENERATED_BODY()

View File

@@ -14,7 +14,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Material_Compile : public UObject, public IWingHandler
class UWing_Material_Compile : public UWingHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Material_Create : public UObject, public IWingHandler
class UWing_Material_Create : public UWingHandler
{
GENERATED_BODY()

View File

@@ -15,7 +15,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Material_DumpParameters : public UObject, public IWingHandler
class UWing_Material_DumpParameters : public UWingHandler
{
GENERATED_BODY()

View File

@@ -15,7 +15,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Property_Dump : public UObject, public IWingHandler
class UWing_Property_Dump : public UWingHandler
{
GENERATED_BODY()

View File

@@ -14,7 +14,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Property_Get : public UObject, public IWingHandler
class UWing_Property_Get : public UWingHandler
{
GENERATED_BODY()

View File

@@ -14,7 +14,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Property_Set : public UObject, public IWingHandler
class UWing_Property_Set : public UWingHandler
{
GENERATED_BODY()

View File

@@ -10,7 +10,7 @@
#include "ShowCommands.generated.h"
UCLASS()
class UWing_ShowCommands : public UObject, public IWingHandler
class UWing_ShowCommands : public UWingHandler
{
GENERATED_BODY()
@@ -21,6 +21,9 @@ public:
UPROPERTY(meta=(Optional, Description="If true, return full details including parameter types and descriptions"))
bool Verbose = false;
UPROPERTY(meta=(Optional, Description="Kind of command: Normal, or Create"))
EWingHandlerKind Kind = EWingHandlerKind::Normal;
virtual FString GetDescription() const override
{
return TEXT("List all available commands with their descriptions.");
@@ -31,16 +34,15 @@ public:
FString QueryLower = Query.ToLower();
FString PrevGroup;
for (UClass* Class : WingUtils::CollectHandlerClasses())
for (const FWingHandlerConfig& H : UWingServer::AllHandlers())
{
FString ToolName = WingUtils::GetHandlerName(Class);
if (!ToolName.ToLower().Contains(QueryLower))
continue;
if (H.Kind != Kind) continue;
if (!H.Name.ToLower().Contains(QueryLower)) continue;
// Blank line between groups
if (!Verbose)
{
FString Group = WingUtils::GetHandlerGroup(Class);
FString Group = WingUtils::GetHandlerGroup(H.Class.Get());
if (Group != PrevGroup)
{
if (!PrevGroup.IsEmpty())
@@ -51,12 +53,20 @@ public:
if (Verbose)
{
WingManual::PrintHandlerHelp(Class);
WingManual::PrintHandlerHelp(H);
}
else
{
WingManual::PrintHandlerPrototype(Class);
WingManual::PrintHandlerPrototype(H);
}
}
if (Kind == EWingHandlerKind::Normal)
{
UWingServer::Printf(TEXT(
"\n"
"You can also use ShowCommands with Kind=Create to see\n"
"commands that create new assets.\n"
"\n"));
}
}
};

View File

@@ -0,0 +1,48 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFactories.h"
#include "SysInfo_Factories.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_SysInfo_Factories : public UWingHandler
{
GENERATED_BODY()
public:
virtual FString GetDescription() const override
{
return TEXT("Sysinfo commands are intended for the human who "
"is developing this plugin, they help him to understand. "
"unreal's internals better.");
}
virtual void Handle() override
{
const TArray<UWingFactories::Info>& All = UWingFactories::AllFactories();
for (int nparam = 0; nparam < 10; nparam++)
{
for (const UWingFactories::Info& Entry : All)
{
if (Entry.Config.Num() != nparam) continue;
if (!Entry.CanCreateNew()) continue;
UWingServer::Printf(TEXT("%s"), *Entry.Name);
for (const FName& Prop : Entry.Config)
{
UWingServer::Printf(TEXT(" PARAM: %s"), *Prop.ToString());
}
UWingServer::Printf(TEXT("\n"));
}
UWingServer::Printf(TEXT("\n"));
}
}
};

View File

@@ -12,7 +12,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Test_Sanitizer : public UObject, public IWingHandler
class UWing_Test_Sanitizer : public UWingHandler
{
GENERATED_BODY()

View File

@@ -12,7 +12,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Test_Tokenizer : public UObject, public IWingHandler
class UWing_Test_Tokenizer : public UWingHandler
{
GENERATED_BODY()

View File

@@ -12,7 +12,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Test_TypeToText : public UObject, public IWingHandler
class UWing_Test_TypeToText : public UWingHandler
{
GENERATED_BODY()

View File

@@ -12,7 +12,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Test_Unsanitize : public UObject, public IWingHandler
class UWing_Test_Unsanitize : public UWingHandler
{
GENERATED_BODY()

View File

@@ -18,7 +18,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_TypeName_Search : public UObject, public IWingHandler
class UWing_TypeName_Search : public UWingHandler
{
GENERATED_BODY()

View File

@@ -6,7 +6,7 @@
#include "UserManual.generated.h"
UCLASS()
class UWing_UserManual : public UObject, public IWingHandler
class UWing_UserManual : public UWingHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Variables_Create : public UObject, public IWingHandler
class UWing_Variables_Create : public UWingHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Variables_Dump : public UObject, public IWingHandler
class UWing_Variables_Dump : public UWingHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Variables_Modify : public UObject, public IWingHandler
class UWing_Variables_Modify : public UWingHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Variables_Remove : public UObject, public IWingHandler
class UWing_Variables_Remove : public UWingHandler
{
GENERATED_BODY()

View File

@@ -19,7 +19,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Widget_Create : public UObject, public IWingHandler
class UWing_Widget_Create : public UWingHandler
{
GENERATED_BODY()

View File

@@ -17,7 +17,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Widget_Delete : public UObject, public IWingHandler
class UWing_Widget_Delete : public UWingHandler
{
GENERATED_BODY()

View File

@@ -17,7 +17,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Widget_Reparent : public UObject, public IWingHandler
class UWing_Widget_Reparent : public UWingHandler
{
GENERATED_BODY()

View File

@@ -12,7 +12,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Widget_SearchTypes : public UObject, public IWingHandler
class UWing_Widget_SearchTypes : public UWingHandler
{
GENERATED_BODY()

View File

@@ -3,7 +3,7 @@
#include "Editor.h"
#include "WingUtils.h"
#include "WingProperty.h"
#include "WingPackageMaker.h"
#include "PackageTools.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Kismet2/EnumEditorUtils.h"
#include "Factories/BlueprintFactory.h"
@@ -62,6 +62,78 @@ void UWingFactories::DisableFactory(const TCHAR* Name)
Entry->Disabled = true;
}
bool UWingFactories::CheckNewAssetPath(const FString& Path)
{
if (UPackageTools::SanitizePackageName(Path) != Path)
{
UWingServer::Printf(TEXT("ERROR: Package path '%s' is not a valid package name\n"), *Path);
return false;
}
if (!FPackageName::IsValidTextForLongPackageName(Path))
{
UWingServer::Printf(TEXT("ERROR: Package path '%s' is not a valid package name\n"), *Path);
return false;
}
if (!Path.StartsWith(TEXT("/Game")))
{
UWingServer::Printf(TEXT("ERROR: Package path '%s' must start with '/Game'\n"), *Path);
return false;
}
if (FindObject<UPackage>(nullptr, *Path))
{
UWingServer::Printf(TEXT("ERROR: An asset already exists at '%s'\n"), *Path);
return false;
}
return true;
}
UPackage *UWingFactories::CreateNewPackage(const FString &Path)
{
UPackage *Pkg = CreatePackage(*Path);
if (!Pkg)
{
UWingServer::Printf(TEXT("ERROR: Failed to create package at '%s'\n"), *Path);
return nullptr;
}
Pkg->ClearFlags(RF_Transient);
Pkg->SetIsExternallyReferenceable(true);
Pkg->MarkPackageDirty();
return Pkg;
}
UObject* UWingFactories::CreateAsset(const FString& Path, UFactory* Factory)
{
// Validate the path, and that there's not already something there.
if (!CheckNewAssetPath(Path)) return nullptr;
FName Name = FName(FPackageName::GetShortName(Path));
// Pre-check: block factories that would cause problems.
// In particular, this blocks those that would pop a dialog.
if (!PreCheck(Factory, Name, Path)) return nullptr;
// Create the asset.
UPackage *Package = CreateNewPackage(Path);
UObject* NewAsset = Factory->FactoryCreateNew(
Factory->GetSupportedClass(),
Package,
Name,
RF_Public | RF_Standalone | RF_Transactional,
nullptr,
GWarn
);
if (!NewAsset)
{
UWingServer::Printf(TEXT("ERROR: Factory '%s' returned null\n"), *Factory->GetClass()->GetName());
return nullptr;
}
UWingServer::Printf(TEXT("Created: %s\n"), *WingUtils::ExternalizeID(Name));
return NewAsset;
}
UObject* UWingFactories::CreateAsset(const FString& Path, const FString& FactoryName, FWingJsonObject& Config)
{
UWingFactories* Self = GEditor->GetEditorSubsystem<UWingFactories>();
@@ -74,18 +146,13 @@ UObject* UWingFactories::CreateAsset(const FString& Path, const FString& Factory
return nullptr;
}
// Make sure this is the a creation factory, as opposed to an import factory.
// Make sure this is a creation factory, as opposed to an import factory.
if (!FactoryInfo->CanCreateNew())
{
UWingServer::Printf(TEXT("ERROR: Factory '%s' cannot create objects from scratch\n"), *FactoryName);
return nullptr;
}
// Validate the path, and that there's not already something there.
WingPackageMaker Maker(Path);
if (!Maker.Ok()) return nullptr;
FName Name = Maker.GetFName();
// Create the factory instance.
UFactory* Factory = NewObject<UFactory>(GetTransientPackage(), FactoryInfo->FactoryClass);
@@ -101,28 +168,7 @@ UObject* UWingFactories::CreateAsset(const FString& Path, const FString& Factory
if (!FWingProperty::PopulateFromJson(Props, ConfigJson.Get(), false))
return nullptr;
// Pre-check: block factories that would cause problems.
// In particular, this blocks those that would pop a dialog.
if (!PreCheck(Factory, Name, Path)) return nullptr;
// Create the asset.
if (!Maker.Make()) return nullptr;
UObject* NewAsset = Factory->FactoryCreateNew(
FactoryInfo->FactoryClass->GetDefaultObject<UFactory>()->GetSupportedClass(),
Maker.Package(),
Name,
RF_Public | RF_Standalone | RF_Transactional,
nullptr,
GWarn
);
if (!NewAsset)
{
UWingServer::Printf(TEXT("ERROR: Factory '%s' returned null\n"), *FactoryName);
return nullptr;
}
UWingServer::Printf(TEXT("Created: %s\n"), *WingUtils::ExternalizeID(Name));
return NewAsset;
return CreateAsset(Path, Factory);
}
bool UWingFactories::PreCheck(UFactory* Factory, FName Name, const FString &Path)

View File

@@ -3,12 +3,12 @@
#include "WingHandler.h"
#include "WingTypes.h"
void WingManual::PrintHandlerPrototype(UClass *HandlerClass)
void WingManual::PrintHandlerPrototype(const FWingHandlerConfig& Handler)
{
UWingServer::Print(WingUtils::GetHandlerName(HandlerClass));
UWingServer::Print(Handler.Name);
UWingServer::Print(TEXT("("));
bool bFirst = true;
for (TFieldIterator<FProperty> PropIt(HandlerClass, EFieldIterationFlags::None); PropIt; ++PropIt)
for (TFieldIterator<FProperty> PropIt(Handler.Class.Get(), EFieldIterationFlags::None); PropIt; ++PropIt)
{
if (!bFirst) UWingServer::Print(TEXT(","));
bFirst = false;
@@ -18,10 +18,10 @@ void WingManual::PrintHandlerPrototype(UClass *HandlerClass)
UWingServer::Print(TEXT(")\n"));
}
void WingManual::PrintHandlerArguments(UClass *HandlerClass)
void WingManual::PrintHandlerArguments(const FWingHandlerConfig& Handler)
{
// parameter details
for (TFieldIterator<FProperty> PropIt(HandlerClass, EFieldIterationFlags::None); PropIt; ++PropIt)
for (TFieldIterator<FProperty> PropIt(Handler.Class.Get(), EFieldIterationFlags::None); PropIt; ++PropIt)
{
FProperty* Prop = *PropIt;
FString Name = Prop->GetName();
@@ -42,23 +42,23 @@ void WingManual::PrintHandlerArguments(UClass *HandlerClass)
}
}
void WingManual::PrintHandlerDescription(UClass *HandlerClass)
void WingManual::PrintHandlerDescription(const FWingHandlerConfig& Handler)
{
const IWingHandler* Handler = Cast<IWingHandler>(HandlerClass->GetDefaultObject());
if (!Handler) return;
UWingServer::Print(WingUtils::WrapText(Handler->GetDescription(), 80, TEXT(" // ")));
const UWingHandler* HandlerCDO = Cast<UWingHandler>(Handler.Class->GetDefaultObject());
if (!HandlerCDO) return;
UWingServer::Print(WingUtils::WrapText(HandlerCDO->GetDescription(), 80, TEXT(" // ")));
}
void WingManual::PrintHandlerHelp(UClass* HandlerClass)
void WingManual::PrintHandlerHelp(const FWingHandlerConfig& Handler)
{
UWingServer::Print(TEXT("\n"));
PrintHandlerPrototype(HandlerClass);
PrintHandlerArguments(HandlerClass);
PrintHandlerDescription(HandlerClass);
PrintHandlerPrototype(Handler);
PrintHandlerArguments(Handler);
PrintHandlerDescription(Handler);
UWingServer::Print(TEXT("\n"));
}
void WingManual::PrintManual(TSet<Section> Sections, UClass *Handler, bool Abridged)
void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* Handler, bool Abridged)
{
if (Sections.IsEmpty()) return;
@@ -71,7 +71,7 @@ void WingManual::PrintManual(TSet<Section> Sections, UClass *Handler, bool Abrid
if (Handler && (Sections.Contains(Section::HandlerHelp) || bPrintAll))
{
PrintHandlerHelp(Handler);
PrintHandlerHelp(*Handler);
}
if (Sections.Contains(Section::Paths) || bPrintAll)

View File

@@ -1,4 +1,5 @@
#include "WingPackageMaker.h"
#include "PackageTools.h"
#include "AssetRegistry/AssetRegistryModule.h"
WingPackageMaker::WingPackageMaker(const FString& InFullPath)
@@ -13,6 +14,11 @@ WingPackageMaker::WingPackageMaker(const FString& InFullPath)
bool WingPackageMaker::CheckNewAssetPath(const FString& Path)
{
if (UPackageTools::SanitizePackageName(Path) != Path)
{
UWingServer::Printf(TEXT("ERROR: Package path '%s' is not a valid package name\n"), *Path);
return false;
}
if (!FPackageName::IsValidTextForLongPackageName(Path))
{
UWingServer::Printf(TEXT("ERROR: Package path '%s' is not a valid package name\n"), *Path);

View File

@@ -265,7 +265,7 @@ FString UWingServer::HandleRequest(const FString& Line)
LogCapture.bEnabled = true;
HandlerOutput.Reset();
SuggestedManualSections.Empty();
LastHandlerClass = nullptr;
LastHandler = nullptr;
TryCallHandler(Line);
@@ -279,7 +279,7 @@ FString UWingServer::HandleRequest(const FString& Line)
if (!SuggestedManualSections.IsEmpty())
{
UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
WingManual::PrintManual(SuggestedManualSections, LastHandlerClass, true);
WingManual::PrintManual(SuggestedManualSections, LastHandler, true);
}
FString Result = HandlerOutput.ToString();
HandlerOutput.Reset();
@@ -312,19 +312,20 @@ void UWingServer::TryCallHandler(const FString &Line)
}
Request->RemoveField(TEXT("command"));
// Find the handler UClass for the specified command.
TObjectPtr<UClass>* HandlerClass = WingHandlerRegistry.Find(Command);
if (!HandlerClass)
// Find the handler for the specified command.
FWingHandlerConfig* Found = FindHandler(Command);
if (!Found)
{
UWingServer::Printf(TEXT("Unknown command: %s"), *Command);
UWingServer::SuggestManual(WingManual::Section::ImportantCommands);
return;
}
LastHandlerClass = *HandlerClass;
LastHandler = Found;
// Make an object of the handler class.
TStrongObjectPtr<UObject> HandlerObj(NewObject<UObject>(GetTransientPackage(), *HandlerClass));
IWingHandler* Handler = Cast<IWingHandler>(HandlerObj.Get());
TStrongObjectPtr<UObject> HandlerObj(NewObject<UObject>(GetTransientPackage(), Found->Class.Get()));
UWingHandler* Handler = Cast<UWingHandler>(HandlerObj.Get());
Handler->ConfigurationObject = Found->Config.Get();
// Populate the handler object with the request parameters.
if (!FWingProperty::PopulateFromJson(HandlerObj->GetClass(), HandlerObj.Get(), &*Request))
@@ -451,10 +452,39 @@ void UWingServer::ClientThreadFunc(UWingServer* Server, TSharedPtr<FClientConnec
// BuildWingHandlerRegistry
// ============================================================
void UWingHandler::Register(UWingServer* Server)
{
UClass* Class = GetClass();
Server->AddHandler(WingUtils::GetHandlerName(Class), Class, nullptr, EWingHandlerKind::Normal);
}
void UWingServer::AddHandler(const FString& Name, UClass* Class, UObject* Config, EWingHandlerKind Kind)
{
FWingHandlerConfig H;
H.Name = Name;
H.Class = TStrongObjectPtr<UClass>(Class);
H.Config = TStrongObjectPtr<UObject>(Config);
H.Kind = Kind;
WingHandlerRegistry.Add(MoveTemp(H));
}
void UWingServer::BuildWingHandlerRegistry()
{
for (UClass* Class : WingUtils::CollectHandlerClasses())
{
WingHandlerRegistry.FindOrAdd(WingUtils::GetHandlerName(Class)) = Class;
UWingHandler* CDO = Cast<UWingHandler>(Class->GetDefaultObject());
CDO->Register(this);
}
WingHandlerRegistry.Sort([](const FWingHandlerConfig& A, const FWingHandlerConfig& B) { return A.Name < B.Name; });
}
FWingHandlerConfig* UWingServer::FindHandler(const FString& Name)
{
int32 Index = Algo::LowerBoundBy(WingHandlerRegistry, Name, [](const FWingHandlerConfig& H) { return H.Name; });
if (Index < WingHandlerRegistry.Num() && WingHandlerRegistry[Index].Name == Name)
{
return &WingHandlerRegistry[Index];
}
return nullptr;
}

View File

@@ -600,7 +600,7 @@ TArray<UClass*> WingUtils::CollectHandlerClasses()
{
UClass* Class = *It;
if (Class->HasAnyClassFlags(CLASS_Abstract)) continue;
if (!Class->ImplementsInterface(UWingHandler::StaticClass())) continue;
if (!Class->IsChildOf(UWingHandler::StaticClass())) continue;
Result.Add(Class);
}
Result.Sort([](UClass& A, UClass& B) { return GetHandlerName(&A) < GetHandlerName(&B); });

View File

@@ -26,17 +26,22 @@ public:
static const TArray<Info>& AllFactories();
// Create an asset on disk, using a factory. Returns the main object.
// Create an asset on disk, using a factory instance. Returns the main object.
// If there are problems, prints error messages and returns nullptr.
static UObject *CreateAsset(const FString &Path, const FString &Factory, FWingJsonObject &Config);
static UObject *CreateAsset(const FString &Path, UFactory *Factory);
// Create an asset on disk, looking up the factory by name and configuring it.
static UObject *CreateAsset(const FString &Path, const FString &FactoryName, FWingJsonObject &Config);
static FString DeriveFactoryName(UClass* FactoryClass);
private:
TArray<Info> Registry;
Info* Find(const FString& Name);
static UPackage *CreateNewPackage(const FString &Path);
static bool CheckNewAssetPath(const FString &Path);
void PopulateRegistry();
void DisableFactory(const TCHAR* Name);
static bool PreCheck(UFactory *Factory, FName Name, const FString &Path);
static FString DeriveFactoryName(UClass* FactoryClass);
};

View File

@@ -1,8 +1,8 @@
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "UObject/Object.h"
#include "UObject/StrongObjectPtr.h"
#include "Dom/JsonObject.h"
#include "WingHandler.generated.h"
@@ -24,32 +24,40 @@ struct FWingJsonArray
TArray<TSharedPtr<FJsonValue>> Array;
};
// Interface for self-registering MCP tool handlers.
//
// Implementing classes declare their parameters as UPROPERTY fields, which are
// automatically populated from the incoming JSON request and used to
// generate the tool's JSON Schema for MCP tools/list.
//
// Class metadata:
// ToolName - the MCP tool name (e.g. "spawn_node")
//
// Property metadata:
// Optional - marks a parameter as not required
//
UINTERFACE(MinimalAPI, meta=(CannotImplementInterfaceInBlueprint))
class UWingHandler : public UInterface
class UWingServer;
UENUM()
enum class EWingHandlerKind
{
GENERATED_BODY()
Normal,
Create
};
class UEWINGMAN_API IWingHandler
USTRUCT()
struct FWingHandlerConfig
{
GENERATED_BODY()
FString Name;
TStrongObjectPtr<UClass> Class;
TStrongObjectPtr<UObject> Config;
EWingHandlerKind Kind = EWingHandlerKind::Normal;
};
UCLASS(Abstract)
class UEWINGMAN_API UWingHandler : public UObject
{
GENERATED_BODY()
public:
// Human-readable tool description for MCP tools/list.
virtual FString GetDescription() const = 0;
virtual FString GetDescription() const PURE_VIRTUAL(UWingHandler::GetDescription, return FString(););
// Register this handler's commands with the server.
virtual void Register(UWingServer* Server);
// Called after parameter fields have been populated from JSON.
virtual void Handle() {}
// The configuration object.
UObject *ConfigurationObject;
};

View File

@@ -1,5 +1,6 @@
#pragma once
#include "Containers/Set.h"
#include "WingHandler.h"
class WingManual
{
@@ -17,9 +18,9 @@ public:
ImportantCommands,
};
static void PrintHandlerPrototype(UClass *Handler);
static void PrintHandlerArguments(UClass *Handler);
static void PrintHandlerDescription(UClass *Handler);
static void PrintHandlerHelp(UClass *Handler);
static void PrintManual(TSet<Section> Sections, UClass *Handler, bool Abridged);
static void PrintHandlerPrototype(const FWingHandlerConfig& Handler);
static void PrintHandlerArguments(const FWingHandlerConfig& Handler);
static void PrintHandlerDescription(const FWingHandlerConfig& Handler);
static void PrintHandlerHelp(const FWingHandlerConfig& Handler);
static void PrintManual(TSet<Section> Sections, const FWingHandlerConfig* Handler, bool Abridged);
};

View File

@@ -9,6 +9,7 @@
#include "WingUtils.h"
#include "WingNotifier.h"
#include "WingLogCapture.h"
#include "WingHandler.h"
#include "WingManual.h"
#include "WingServer.generated.h"
@@ -72,19 +73,22 @@ public:
/** Port the server is listening on. */
int32 GetPort() const { return Port; }
// ----- Tool dispatch -----
void AddHandler(const FString& Name, UClass* Class, UObject* Config, EWingHandlerKind Kind);
static const TArray<FWingHandlerConfig>& AllHandlers() { return GWingServer->WingHandlerRegistry; }
private:
static UWingServer* GWingServer;
// ----- Tool dispatch -----
UPROPERTY()
FWingNotifier Notifier;
UClass* LastHandlerClass;
FWingHandlerConfig* LastHandler;
TStringBuilder<16384> HandlerOutput;
TSet<WingManual::Section> SuggestedManualSections;
FLogCaptureOutputDevice LogCapture; // installed once at startup, enabled per-request
UPROPERTY()
TMap<FString, TObjectPtr<UClass>> WingHandlerRegistry; // tool name -> UWingHandler subclass
TArray<FWingHandlerConfig> WingHandlerRegistry; // sorted by Name
void BuildWingHandlerRegistry();
FWingHandlerConfig* FindHandler(const FString& Name);
// Handle a complete JSON line and return the response JSON
FString HandleRequest(const FString& Line);