Files
UnrealEngine/Engine/Source/Editor/Kismet/Private/ProjectUtilities/BuildTargetSet.cpp
2025-05-18 13:04:45 +08:00

296 lines
8.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ProjectUtilities/BuildTargetSet.h"
#include "Interfaces/IProjectManager.h"
#include "Interfaces/IPluginManager.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Logging/StructuredLog.h"
#include "ProfilingDebugging/ScopedTimers.h"
#include "ProjectDescriptor.h"
#include "IHotReload.h"
#if WITH_LIVE_CODING
#include "ILiveCodingModule.h"
#endif
namespace UE::Private
{
const UClass* GetNativeClass(const UFunction* ForFunction);
const UClass* GetNativeClass(const UClass* ForClass);
void InvalidateCache(bool bIsAsyncCompile);
void InvalidateCache();
static TMap<FName, EHostType::Type> DescriptorCache;
static FCriticalSection DescriptorCacheLock;
}
const UClass* UE::Private::GetNativeClass(const UFunction* ForFunction)
{
const UClass* NativeClass = GetNativeClass(ForFunction->GetOwnerClass());
if (NativeClass)
{
return NativeClass;
}
ensureMsgf(false, TEXT("Found no native base class for function - cannot validate native constraints: %s"), *ForFunction->GetPathName());
return nullptr;
}
const UClass* UE::Private::GetNativeClass(const UClass* ForClass)
{
return FBlueprintEditorUtils::FindFirstNativeClass(const_cast<UClass*>(ForClass));
}
void UE::Private::InvalidateCache(bool bIsAsyncCompile)
{
InvalidateCache();
}
void UE::Private::InvalidateCache()
{
FScopeLock WriteLock(&DescriptorCacheLock);
DescriptorCache.Reset();
}
namespace UE::ProjectUtilities
{
const EHostType::Type FindModuleDescriptorHostType(const UPackage* ForNativePackage)
{
if (!ForNativePackage)
{
return EHostType::Max;
}
// The loops at the bottom are naive and may need to be optimized if we begin
// validating build target compatibility widely:
FString FunctionPackageName = ForNativePackage->GetName();
if (!FunctionPackageName.StartsWith(TEXT("/Script/")))
{
return EHostType::Max;
}
FunctionPackageName = FunctionPackageName.RightChop(8); // Chop "/Script/" from the name
if (FunctionPackageName == TEXT("Engine"))
{ // common case, there is no ModuleDescriptor for Engine
return EHostType::Max;
}
const IProjectManager& ProjectManager = IProjectManager::Get();
const FProjectDescriptor* PD = ProjectManager.GetCurrentProject();
if (!PD)
{
return EHostType::Max;
}
const FName FunctionPackageFName(FunctionPackageName.Len(), *FunctionPackageName);
const auto AddToCache = [](FName Name, EHostType::Type HostType)
{
// must be a write lock:
FScopeLock WriteLock(&UE::Private::DescriptorCacheLock);
static bool bCheckedForHotReloadOrLiveCoding = false;
if(!bCheckedForHotReloadOrLiveCoding)
{
bCheckedForHotReloadOrLiveCoding = true;
// Register the hot-reload callback - invalidating our cache when a module is compiled:
if(IHotReloadModule::IsAvailable())
{
IHotReloadModule::Get().OnModuleCompilerStarted().AddStatic( &UE::Private::InvalidateCache );
}
#if WITH_LIVE_CODING
if (ILiveCodingModule* LiveCoding = FModuleManager::LoadModulePtr<ILiveCodingModule>(LIVE_CODING_MODULE_NAME))
{
LiveCoding->GetOnPatchCompleteDelegate().AddStatic(&UE::Private::InvalidateCache);
}
#endif
}
UE::Private::DescriptorCache.Add(Name, HostType);
};
const auto FindFromCache = [](FName Name) -> TOptional<EHostType::Type>
{
// could be a read lock:
FScopeLock FindLock(&UE::Private::DescriptorCacheLock);
const EHostType::Type* Result = UE::Private::DescriptorCache.Find(Name);
return Result ? *Result : TOptional<EHostType::Type>();
};
TOptional<EHostType::Type> CachedDescriptor = FindFromCache(FunctionPackageFName);
if (CachedDescriptor)
{
return *CachedDescriptor;
}
const FModuleDescriptor* OwningMD = nullptr;
for (const FModuleDescriptor& MD : PD->Modules)
{
if (MD.Name == FunctionPackageFName)
{
AddToCache(FunctionPackageFName, MD.Type);
return MD.Type;
}
}
// This is a little expensive, hence the cache - IPluginManager could take over
// acceleration structure duties:
TArray<TSharedRef<IPlugin>> AllPlugins = IPluginManager::Get().GetDiscoveredPlugins();
for (const TSharedRef<IPlugin>& Plugin : AllPlugins)
{
if (Plugin->IsHidden())
{
continue;
}
for (const FModuleDescriptor& MD : Plugin->GetDescriptor().Modules)
{
if (MD.Name == FunctionPackageFName)
{
AddToCache(FunctionPackageFName, MD.Type);
return MD.Type;
}
}
}
AddToCache(FunctionPackageFName, EHostType::Max);
return EHostType::Max;
}
FBuildTargetSet FBuildTargetSet::GetCallerTargetsUnsupportedByCallee(const UClass* Caller, const UFunction* Callee)
{
using namespace UE::Private;
check(Caller && Callee);
if (Caller == Callee->GetOwnerClass())
{
return FBuildTargetSet();
}
const UClass* NativeCaller = GetNativeClass(Caller);
const UClass* NativeCallee = GetNativeClass(Callee);
// if the native modules are the same type then we are fine:
const UPackage* CallerNativePackage = NativeCaller->GetPackage();
const UPackage* CalleeNativePackage = NativeCallee->GetPackage();
if (CallerNativePackage == CalleeNativePackage)
{
return FBuildTargetSet();
}
FBuildTargetSet CallerSet;
CallerSet.BuildTargetFlags = GetSupportedTargetsForNativeClass(NativeCaller);
FBuildTargetSet CalleeSet;
CalleeSet.BuildTargetFlags = GetSupportedTargetsForNativeClass(NativeCallee);
FBuildTargetSet Result;
Result.BuildTargetFlags = GetCallerTargetsUnsupportedByCalleeImpl(CallerSet.BuildTargetFlags, CalleeSet.BuildTargetFlags);
return Result;
}
FString FBuildTargetSet::LexToString() const
{
return LexToStringImpl(BuildTargetFlags);
}
FString FBuildTargetSet::LexToStringImpl(EBuildTargetFlags Flags)
{
FString Result;
const TCHAR* Seperator = TEXT("");
if ((Flags & EBuildTargetFlags::Server) != EBuildTargetFlags::None)
{
Result += TEXT("Server");
Seperator = TEXT("|");
}
if ((Flags & EBuildTargetFlags::Client) != EBuildTargetFlags::None)
{
Result += Seperator;
Result += TEXT("Client");
Seperator = TEXT("|");
}
if ((Flags & EBuildTargetFlags::Editor) != EBuildTargetFlags::None)
{
Result += Seperator;
Result += TEXT("Editor");
Seperator = TEXT("|");
}
return Result;
}
FBuildTargetSet::EBuildTargetFlags FBuildTargetSet::GetSupportedTargetsForNativeClass(const UClass* NativeBase)
{
check(NativeBase);
const EBuildTargetFlags ALL = EBuildTargetFlags::Server
| EBuildTargetFlags::Client
| EBuildTargetFlags::Editor;
using namespace UE::Private;
const UPackage* Package = NativeBase->GetPackage();
const EHostType::Type ModuleHostType = UE::ProjectUtilities::FindModuleDescriptorHostType(Package);
EBuildTargetFlags SupportedTargets = ALL;
if (ModuleHostType != EHostType::Max)
{
switch (ModuleHostType)
{
case EHostType::Runtime:
case EHostType::RuntimeNoCommandlet:
case EHostType::RuntimeAndProgram:
case EHostType::CookedOnly:
break;
case EHostType::UncookedOnly:
case EHostType::Developer:
case EHostType::DeveloperTool:
case EHostType::Editor:
case EHostType::EditorNoCommandlet:
case EHostType::EditorAndProgram:
case EHostType::Program:
SupportedTargets = EBuildTargetFlags::Editor;
break;
case EHostType::ServerOnly:
// Loads on all targets except dedicated clients.
SupportedTargets = EBuildTargetFlags::Server
| EBuildTargetFlags::Editor;
break;
case EHostType::ClientOnly:
case EHostType::ClientOnlyNoCommandlet:
// Loads on all targets except dedicated servers.
SupportedTargets = EBuildTargetFlags::Client
| EBuildTargetFlags::Editor;
break;
default:
ensureMsgf(false, TEXT("Encountered unexpected module type: %d in module %s"), ModuleHostType, Package ? *Package->GetName() : TEXT("nullpackage"));
break;
}
}
// honor IsEditorOnlyObject
if (IsEditorOnlyObject(NativeBase))
{
SupportedTargets = EBuildTargetFlags::Editor;
}
// if the class itself doesn't think it needs load for server
if (!NativeBase->NeedsLoadForServer() ||
!NativeBase->GetDefaultObject()->NeedsLoadForServer())
{
SupportedTargets &= ~EBuildTargetFlags::Server;
}
// or client, then we can't support those:
if (!NativeBase->NeedsLoadForClient()||
!NativeBase->GetDefaultObject()->NeedsLoadForClient())
{
SupportedTargets &= ~EBuildTargetFlags::Client;
}
return SupportedTargets;
}
FBuildTargetSet::EBuildTargetFlags FBuildTargetSet::GetCallerTargetsUnsupportedByCalleeImpl(FBuildTargetSet::EBuildTargetFlags CallerTargets, FBuildTargetSet::EBuildTargetFlags CalleeTargets)
{
using namespace UE::Private;
// caller targets must be a subset of valid callee targets,
// otherwise caller could be deployed into a context within which
// the callee is not available:
EBuildTargetFlags Differences = CallerTargets ^ CalleeTargets;
return (Differences & ~CalleeTargets);
}
}