More MCP work

This commit is contained in:
2026-03-08 03:44:27 -04:00
parent 0fe0cfa1c2
commit a72b65641e
13 changed files with 381 additions and 922 deletions

View File

@@ -1,248 +1,12 @@
#include "MCPAssetFinder.h"
#include "Engine/Engine.h"
#include "Engine/Blueprint.h"
#include "Engine/World.h"
#include "Engine/Level.h"
#include "Engine/LevelScriptBlueprint.h"
#include "Materials/Material.h"
#include "Materials/MaterialInstanceConstant.h"
#include "Materials/MaterialFunction.h"
#include "Animation/Skeleton.h"
#include "Animation/AnimSequence.h"
#include "Animation/BlendSpace.h"
#include "Animation/AnimBlueprint.h"
#include "AnimationStateMachineGraph.h"
#include "MCPUtils.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
const TArray<FAssetData> UMCPAssetFinder::EmptyAssetArray;
// ============================================================
// Initialize / Deinitialize — subscribe to asset registry events
// ============================================================
void UMCPAssetFinder::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
AR.OnAssetAdded().AddUObject(this, &UMCPAssetFinder::OnAssetEvent);
AR.OnAssetRemoved().AddUObject(this, &UMCPAssetFinder::OnAssetEvent);
AR.OnAssetUpdated().AddUObject(this, &UMCPAssetFinder::OnAssetEvent);
AR.OnAssetRenamed().AddUObject(this, &UMCPAssetFinder::OnAssetRenamed);
}
void UMCPAssetFinder::Deinitialize()
{
IAssetRegistry* AR = IAssetRegistry::Get();
if (AR)
{
AR->OnAssetAdded().RemoveAll(this);
AR->OnAssetRemoved().RemoveAll(this);
AR->OnAssetUpdated().RemoveAll(this);
AR->OnAssetRenamed().RemoveAll(this);
}
Super::Deinitialize();
}
// ============================================================
// Get / Refresh
// ============================================================
UMCPAssetFinder* UMCPAssetFinder::Get()
{
UMCPAssetFinder* Self = GEngine ? GEngine->GetEngineSubsystem<UMCPAssetFinder>() : nullptr;
checkf(Self, TEXT("MCPAssetFinder::Get() called before engine initialization"));
return Self;
}
void UMCPAssetFinder::CacheAssets(IAssetRegistry &Registry, UClass *Class, bool IncludeSubclasses)
{
FName Key = Class->GetFName();
TArray<FAssetData>& List = AssetCache.Add(Key);
Registry.GetAssetsByClass(Class->GetClassPathName(), List, IncludeSubclasses);
}
void UMCPAssetFinder::Refresh()
{
checkf(IsInGameThread(), TEXT("MCPAssetFinder must only be accessed from the game thread"));
UMCPAssetFinder* Self = Get();
IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
while (AR.IsLoadingAssets()) FPlatformProcess::Sleep(0.1f);
if (!Self->bDirty) return;
Self->AssetCache.Empty();
// Cache all asset classes.
Self->CacheAssets(AR, UBlueprint::StaticClass(), true);
Self->CacheAssets(AR, UWorld::StaticClass(), false);
Self->CacheAssets(AR, UMaterial::StaticClass(), false);
Self->CacheAssets(AR, UMaterialInstanceConstant::StaticClass(), false);
Self->CacheAssets(AR, UMaterialFunction::StaticClass(), false);
Self->CacheAssets(AR, UUserDefinedStruct::StaticClass(), false);
Self->CacheAssets(AR, UUserDefinedEnum::StaticClass(), false);
Self->CacheAssets(AR, USkeleton::StaticClass(), false);
Self->CacheAssets(AR, UAnimSequence::StaticClass(), false);
Self->CacheAssets(AR, UBlendSpace::StaticClass(), false);
Self->CacheAssets(AR, UAnimBlueprint::StaticClass(), false);
// Combined list: blueprints + maps
TArray<FAssetData>& Combined = Self->AssetCache.Add(BlueprintsAndMaps);
Combined = Self->AssetCache[UBlueprint::StaticClass()->GetFName()];
Combined.Append(Self->AssetCache[UWorld::StaticClass()->GetFName()]);
Self->bDirty = false;
UE_LOG(LogTemp, Display, TEXT("MCPAssetFinder: Refreshed — %d asset types cached"),
Self->AssetCache.Num());
}
// ============================================================
// Static asset list accessors
// ============================================================
const TArray<FAssetData>& UMCPAssetFinder::GetAssets(FName Key)
{
TArray<FAssetData>* Found = Get()->AssetCache.Find(Key);
return Found ? *Found : EmptyAssetArray;
}
const TArray<FAssetData>& UMCPAssetFinder::GetAssets(UClass* Class)
{
return GetAssets(Class->GetFName());
}
FName UMCPAssetFinder::BlueprintsAndMaps = FName(TEXT("BlueprintsAndMaps"));
// ============================================================
// Find / Search helpers
// ============================================================
FAssetData* UMCPAssetFinder::FindAsset(FName Class, const FString& NameOrPath, FString* OutError)
{
const TArray<FAssetData>& List = GetAssets(Class);
bool IsPath = NameOrPath.Contains(TEXT("/"));
FAssetData* Found = nullptr;
for (const FAssetData& Asset : List)
{
FName Name = IsPath ? Asset.PackageName : Asset.AssetName;
if (!Name.ToString().Equals(NameOrPath, ESearchCase::IgnoreCase)) continue;
if (!Found)
{
Found = const_cast<FAssetData*>(&Asset);
continue;
}
if (OutError)
{
*OutError = FString::Printf(
TEXT("Ambiguous asset name '%s' — matches both '%s' and '%s'. Use the full package path to disambiguate."),
*NameOrPath, *Found->PackageName.ToString(), *Asset.PackageName.ToString());
}
return nullptr;
}
return Found;
}
FAssetData* UMCPAssetFinder::FindAsset(UClass* Class, const FString& NameOrPath, FString* OutError)
{
return FindAsset(Class->GetFName(), NameOrPath, OutError);
}
TArray<FAssetData*> UMCPAssetFinder::SearchAssets(FName Class, const FString& Filter, FString* OutError)
{
const TArray<FAssetData>& List = GetAssets(Class);
TArray<FAssetData*> Results;
for (const FAssetData& Asset : List)
{
FString AssetName = Asset.AssetName.ToString();
FString PackagePath = Asset.PackageName.ToString();
if (AssetName.Contains(Filter, ESearchCase::IgnoreCase) ||
PackagePath.Contains(Filter, ESearchCase::IgnoreCase))
{
Results.Add(const_cast<FAssetData*>(&Asset));
}
}
return Results;
}
TArray<FAssetData*> UMCPAssetFinder::SearchAssets(UClass* Class, const FString& Filter, FString* OutError)
{
return SearchAssets(Class->GetFName(), Filter, OutError);
}
FAssetData* UMCPAssetFinder::FindAnyAsset(const FString& NameOrPath, FString* OutError)
{
FAssetData* Found = nullptr;
for (auto& Pair : Get()->AssetCache)
{
if (Pair.Key == BlueprintsAndMaps) continue;
FString LocalError;
FAssetData* Asset = FindAsset(Pair.Key, NameOrPath, &LocalError);
if (!LocalError.IsEmpty())
{
if (OutError) *OutError = LocalError;
return nullptr;
}
if (!Asset) continue;
if (Found)
{
if (OutError)
{
*OutError = FString::Printf(
TEXT("Ambiguous asset name '%s' — matches '%s' and '%s'. Use the full package path to disambiguate."),
*NameOrPath, *Found->PackageName.ToString(), *Asset->PackageName.ToString());
}
return nullptr;
}
Found = Asset;
}
return Found;
}
// ============================================================
// Load helpers
// ============================================================
UBlueprint* UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(FAssetData& Asset, MCPErrorCallback Error)
{
// Regular blueprint asset
UBlueprint* BP = Cast<UBlueprint>(Asset.GetAsset());
if (BP) return BP;
// Map asset — extract the level blueprint
UWorld* World = Cast<UWorld>(Asset.GetAsset());
if (World && World->PersistentLevel)
{
ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(true);
if (LevelBP) return LevelBP;
}
Error.SetError(FString::Printf(TEXT("Asset '%s' loaded but its level blueprint could not be retrieved."),
*Asset.AssetName.ToString()));
return nullptr;
}
UBlueprint* UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(const FString& NameOrPath, MCPErrorCallback Error)
{
FString FindError;
FAssetData* Asset = FindAsset(BlueprintsAndMaps, NameOrPath, &FindError);
if (!Asset)
{
if (FindError.IsEmpty())
FindError = FString::Printf(TEXT("Blueprint '%s' not found."), *NameOrPath);
Error.SetError(FindError);
return nullptr;
}
return LoadBlueprintOrLevelBlueprint(*Asset, Error);
}
// ============================================================
// MCPAssetsBase
// ============================================================
@@ -263,7 +27,7 @@ MCPAssetsBase& MCPAssetsBase::Exact(const FString& InName)
MCPAssetsBase& MCPAssetsBase::Substring(const FString& InFilter)
{
MatchName = InFilter;
bExactMatch = false;
bExactMatch = false;
bPatternHasSlash = MatchName.Contains(TEXT("/"));
return *this;
}
@@ -411,20 +175,3 @@ void MCPAssetsBase::SetError(const FString &Msg)
UObjectResults.Empty();
ErrorCB.SetError(Msg);
}
// ============================================================
// Load helpers
// ============================================================
UAnimationStateMachineGraph* UMCPAssetFinder::LoadAnimStateMachineGraph(
const FString& BlueprintName, const FString& GraphName, MCPErrorCallback Error)
{
UAnimBlueprint* AnimBP = LoadAsset<UAnimBlueprint>(BlueprintName, Error);
if (!AnimBP) return nullptr;
UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(AnimBP, GraphName);
if (!SMGraph)
Error.SetError(FString::Printf(TEXT("State machine graph '%s' not found in '%s'"), *GraphName, *BlueprintName));
return SMGraph;
}