13#include <util/ue-header-guard-begin.h>
14#include "AssetRegistry/AssetRegistryState.h"
15#include "AssetRegistry/IAssetRegistry.h"
16#include "Engine/Engine.h"
17#include "Engine/LevelStreaming.h"
18#include "Engine/World.h"
19#include "GenericPlatform/GenericPlatformProperties.h"
20#include "HAL/FileManager.h"
21#include "HAL/PlatformMisc.h"
22#include "HAL/PlatformProperties.h"
23#include "Interfaces/IPluginManager.h"
24#include "Misc/CommandLine.h"
25#include "Misc/CoreDelegates.h"
26#include "Misc/EngineVersion.h"
27#include "Misc/FileHelper.h"
28#include "Misc/PackageName.h"
29#include "Misc/Parse.h"
30#include "Misc/Paths.h"
31#include "UObject/Package.h"
32#include "UObject/UObjectGlobals.h"
33#include "UObject/UObjectIterator.h"
34#include <util/ue-header-guard-end.h>
40 if (GEngine ==
nullptr)
49 Super::Initialize(Collection);
54 if (IsRunningCommandlet() || GIsEditor)
56 UE_LOG(LogCarla, Log, TEXT(
"Content packs: auto-discovery skipped (editor/commandlet process)"));
64 Super::Deinitialize();
74 Dirs.Add(FPaths::ConvertRelativePathToFull(FPaths::ProjectDir() / TEXT(
"Packs")));
76 auto AppendList = [&Dirs](
const FString &List)
78 TArray<FString> Parts;
79 List.ParseIntoArray(Parts, TEXT(
";"),
true);
80 for (FString &Part : Parts)
82 Part.TrimStartAndEndInline();
83 Part = Part.TrimQuotes();
86 Dirs.AddUnique(FPaths::ConvertRelativePathToFull(Part));
93 FString FromCommandLine;
94 if (FParse::Value(FCommandLine::Get(), TEXT(
"-carla-packs="), FromCommandLine))
96 AppendList(FromCommandLine);
99 const FString FromEnv = FPlatformMisc::GetEnvironmentVariable(TEXT(
"CARLA_PACKS"));
100 if (!FromEnv.IsEmpty())
117 UE_LOG(LogCarla, Log, TEXT(
"Content packs: base release stamp '%s'"),
118 Stamp.IsEmpty() ? TEXT(
"<none: editor/dev build>") : *Stamp);
122 if (!FPaths::DirectoryExists(Dir))
124 UE_LOG(LogCarla, Log, TEXT(
"Content packs: directory '%s' does not exist, skipping"), *Dir);
128 TArray<FString> Children;
129 IFileManager::Get().FindFiles(Children, *(Dir / TEXT(
"*")),
false,
true);
131 for (
const FString &Child : Children)
134 if (!FPaths::FileExists(Manifest))
139 if (!
Mount(Manifest, Error))
141 UE_LOG(LogCarla, Warning, TEXT(
"Content packs: '%s' not mounted: %s"), *Manifest, *Error);
149 for (
const TSharedRef<IPlugin> &Plugin : IPluginManager::Get().GetEnabledPluginsWithContent())
152 if (!Plugin->IsMounted() || !FPaths::FileExists(Manifest) ||
FindPack(Plugin->GetName()) !=
nullptr)
160 UE_LOG(LogCarla, Warning, TEXT(
"Content packs: engine-mounted plugin '%s' not adopted: %s"),
161 *Plugin->GetName(), *Error);
164 if (!Parsed.
Name.Equals(Plugin->GetName(), ESearchCase::IgnoreCase))
166 UE_LOG(LogCarla, Warning,
167 TEXT(
"Content packs: engine-mounted plugin '%s' not adopted: manifest name '%s' does not match the plugin name"),
168 *Plugin->GetName(), *Parsed.
Name);
171 if (!
Mount(Manifest, Error))
173 UE_LOG(LogCarla, Warning, TEXT(
"Content packs: engine-mounted plugin '%s' not adopted: %s"),
174 *Plugin->GetName(), *Error);
187 if (Pack.GetName().Equals(Name, ESearchCase::IgnoreCase))
205 if (Map.Name.Equals(MapName, ESearchCase::IgnoreCase))
216 TArray<FString> Names;
223 Names.AddUnique(Map.Name);
232 TArray<FString> Dirs;
237 Dirs.Add(Pack.ContentDir);
245 TArray<FString> Files;
247 if (Manager ==
nullptr)
253 const FString File = ContentDir / TEXT(
"Config") / RelativeName;
254 if (IFileManager::Get().FileExists(*File))
265 const FString StampFile = FPaths::ProjectDir() / TEXT(
"BaseRelease");
266 if (FPaths::FileExists(StampFile) && FFileHelper::LoadFileToString(Stamp, *StampFile))
268 Stamp.TrimStartAndEndInline();
279 FString &OutError)
const
284 UE_LOG(LogCarla, Warning,
285 TEXT(
"Content pack '%s': this build has no BaseRelease stamp (editor/dev build); "
286 "skipping the base release check (pack was built for '%s')"),
291 UE_LOG(LogCarla, Warning,
292 TEXT(
"Content pack '%s': manifest has no base_release; this build is '%s'"),
293 *Manifest.
Name, *Stamp);
295 else if (!Manifest.
BaseRelease.Equals(Stamp, ESearchCase::IgnoreCase))
297 OutError = FString::Printf(
298 TEXT(
"pack '%s' was built against base release '%s' but this server is '%s'; "
299 "rebuild the pack against this release"),
304 const FString ThisPlatform = FPlatformProperties::IniPlatformName();
306 const FString PackPlatform = Manifest.
Platform.Equals(TEXT(
"Win64"), ESearchCase::IgnoreCase)
307 ? FString(TEXT(
"Windows"))
311 UE_LOG(LogCarla, Warning,
312 TEXT(
"Content pack '%s': manifest has no platform; this server runs on '%s'"),
313 *Manifest.
Name, *ThisPlatform);
315 else if (!PackPlatform.Equals(ThisPlatform, ESearchCase::IgnoreCase))
317 OutError = FString::Printf(
318 TEXT(
"pack '%s' was built for platform '%s' but this server runs on '%s'"),
325 const FEngineVersion &Current = FEngineVersion::Current();
326 const FString ThisEngine = FString::Printf(TEXT(
"%u.%u.%u"),
327 Current.GetMajor(), Current.GetMinor(), Current.GetPatch());
330 UE_LOG(LogCarla, Warning,
331 TEXT(
"Content pack '%s': built with engine %s, this server runs %s"),
348 if (!FPlatformProperties::RequiresCookedData())
358 const FString PaksDir = Pack.
ContentDir / TEXT(
"Paks") / FPlatformProperties::PlatformName();
359 TArray<FString> PakFiles;
360 IFileManager::Get().FindFiles(PakFiles, *(PaksDir / TEXT(
"*.pak")),
true,
false);
363 if (PakFiles.Num() == 0)
365 TArray<FString> TocFiles;
366 IFileManager::Get().FindFiles(TocFiles, *(PaksDir / TEXT(
"*.utoc")),
true,
false);
367 if (TocFiles.Num() > 0)
369 OutError = FString::Printf(
370 TEXT(
"'%s' has %d .utoc container(s) but no .pak next to them; the stager always writes the "
371 ".pak stub, ship all three of .pak/.utoc/.ucas"), *PaksDir, TocFiles.Num());
374 UE_LOG(LogCarla, Warning,
375 TEXT(
"Content pack '%s': no .pak under '%s'; only loose files (if any) will be visible"),
380 if (!FCoreDelegates::MountPak.IsBound())
382 OutError = TEXT(
"pak mounting is unavailable in this process (FCoreDelegates::MountPak not bound)");
386 for (
const FString &PakName : PakFiles)
388 const FString PakPath = PaksDir / PakName;
389 if (FCoreDelegates::MountPak.Execute(PakPath, 0) ==
nullptr)
391 OutError = FString::Printf(TEXT(
"failed to mount pak '%s'"), *PakPath);
395 UE_LOG(LogCarla, Log, TEXT(
"Content pack '%s': mounted pak '%s'"), *Pack.
GetName(), *PakPath);
403 for (int32 i = Pack.
MountedPaks.Num() - 1; i >= 0; --i)
406 if (FCoreDelegates::OnUnmountPak.IsBound() && FCoreDelegates::OnUnmountPak.Execute(PakPath))
408 UE_LOG(LogCarla, Log, TEXT(
"Content pack '%s': unmounted pak '%s'"), *Pack.
GetName(), *PakPath);
412 UE_LOG(LogCarla, Warning, TEXT(
"Content pack '%s': failed to unmount pak '%s'"), *Pack.
GetName(), *PakPath);
420 IPluginManager &PluginManager = IPluginManager::Get();
421 const FString &Name = Pack.
GetName();
423 TSharedPtr<IPlugin> Existing = PluginManager.FindPlugin(Name);
424 if (Existing.IsValid())
426 const FString ExistingFile = FPaths::ConvertRelativePathToFull(Existing->GetDescriptorFileName());
427 if (!FPaths::IsSamePath(ExistingFile, Pack.
PluginFile))
429 OutError = FString::Printf(
430 TEXT(
"pack name '%s' collides with the plugin at '%s'"), *Name, *ExistingFile);
433 if (Existing->IsEnabled() && FPackageName::MountPointExists(FString::Printf(TEXT(
"/%s/"), *Name)))
435 OutError = FString::Printf(TEXT(
"plugin '%s' is already mounted"), *Name);
439 bool bAddedNow =
false;
440 if (!Existing.IsValid())
443 if (!PluginManager.AddToPluginsList(Pack.
PluginFile, &FailReason))
445 OutError = FString::Printf(TEXT(
"cannot register plugin '%s': %s"),
452 if (!PluginManager.MountExplicitlyLoadedPlugin(Name))
454 OutError = FString::Printf(
455 TEXT(
"MountExplicitlyLoadedPlugin('%s') failed; the .uplugin must set "
456 "\"ExplicitlyLoaded\": true and \"CanContainContent\": true"), *Name);
461 PluginManager.RemoveFromPluginsList(Pack.
PluginFile);
466 const FString MountPoint = FString::Printf(TEXT(
"/%s/"), *Name);
467 if (!FPackageName::MountPointExists(MountPoint))
469 OutError = FString::Printf(TEXT(
"mount point '%s' was not registered"), *MountPoint);
473 TSharedPtr<IPlugin> Plugin = PluginManager.FindPlugin(Name);
474 if (Plugin.IsValid())
476 Pack.
ContentDir = FPaths::ConvertRelativePathToFull(Plugin->GetContentDir());
478 UE_LOG(LogCarla, Log, TEXT(
"Content pack '%s': mounted '%s' -> '%s'"),
485 IAssetRegistry *AssetRegistry = IAssetRegistry::Get();
486 if (AssetRegistry ==
nullptr)
488 UE_LOG(LogCarla, Warning, TEXT(
"Content pack '%s': asset registry unavailable"), *Pack.
GetName());
492 const FString MountPoint = FString::Printf(TEXT(
"/%s/"), *Pack.
GetName());
493 const FString StateFile = Pack.
PackDir / TEXT(
"AssetRegistry.bin");
495 if (FPaths::FileExists(StateFile))
497 if (AssetRegistry->ActiveMountsRegisterAndLoadFilePathSynchronous(StateFile))
499 UE_LOG(LogCarla, Log, TEXT(
"Content pack '%s': loaded asset registry '%s'"),
503 FAssetRegistryState
State;
504 if (FAssetRegistryState::LoadFromDisk(*StateFile, FAssetRegistryLoadOptions(),
State))
506 AssetRegistry->AppendState(
State);
507 UE_LOG(LogCarla, Log, TEXT(
"Content pack '%s': appended asset registry '%s'"),
511 UE_LOG(LogCarla, Warning,
512 TEXT(
"Content pack '%s': cannot load '%s', falling back to a synchronous scan of %s"),
513 *Pack.
GetName(), *StateFile, *MountPoint);
517 UE_LOG(LogCarla, Warning,
518 TEXT(
"Content pack '%s': no AssetRegistry.bin, scanning %s synchronously"),
522 AssetRegistry->ScanPathsSynchronous({MountPoint},
true);
530 if (OutPack !=
nullptr)
536 OutError = TEXT(
"empty pack path");
540 FString ManifestPath = FPaths::ConvertRelativePathToFull(Path);
541 if (FPaths::DirectoryExists(ManifestPath))
545 if (!FPaths::FileExists(ManifestPath))
547 OutError = FString::Printf(
548 TEXT(
"'%s' is not a content pack: no directory or manifest at '%s'"), *Path, *ManifestPath);
559 int32 Index = INDEX_NONE;
560 for (int32 i = 0; i <
Packs.Num(); ++i)
562 if (
Packs[i].GetName().Equals(Manifest.
Name, ESearchCase::IgnoreCase))
564 if (
Packs[i].bMounted)
566 OutError = FString::Printf(TEXT(
"pack '%s' is already mounted from '%s'"),
568 if (OutPack !=
nullptr)
570 *OutPack = &
Packs[i];
592 OutError = FString::Printf(TEXT(
"pack '%s' has no plugin descriptor '%s'"),
596 if (!FPaths::DirectoryExists(Pack.
ContentDir))
598 UE_LOG(LogCarla, Warning, TEXT(
"Content pack '%s': no Content directory at '%s'"),
605 TSharedPtr<IPlugin> EnginePlugin = IPluginManager::Get().FindPlugin(Manifest.
Name);
606 if (EnginePlugin.IsValid())
609 const FString ExistingFile = FPaths::ConvertRelativePathToFull(EnginePlugin->GetDescriptorFileName());
610 if (!FPaths::IsSamePath(ExistingFile, Pack.
PluginFile))
612 OutError = FString::Printf(
613 TEXT(
"pack name '%s' collides with the plugin at '%s'"), *Manifest.
Name, *ExistingFile);
617 if (EnginePlugin.IsValid() && EnginePlugin->IsMounted())
620 Pack.
ContentDir = FPaths::ConvertRelativePathToFull(EnginePlugin->GetContentDir());
621 UE_LOG(LogCarla, Log, TEXT(
"Content pack '%s': plugin already mounted by the engine, adopting it"),
639 if (Index == INDEX_NONE)
641 Index =
Packs.Add(MoveTemp(Pack));
645 Packs[Index] = MoveTemp(Pack);
650 UE_LOG(LogCarla, Log, TEXT(
"Content pack '%s' %s mounted from '%s' (%d maps: %s)"),
653 if (OutPack !=
nullptr)
661 for (TObjectIterator<UActorDispatcher> DispatcherIt; DispatcherIt; ++DispatcherIt)
667 for (TObjectIterator<ACarlaActorFactory> FactoryIt; FactoryIt; ++FactoryIt)
671 DispatcherIt->BindNewDefinitions(**FactoryIt);
690 TArray<FString> Prefixes;
691 Prefixes.Add(FString::Printf(TEXT(
"/%s/"), *Pack.
GetName()));
694 if (!Map.Package.StartsWith(TEXT(
"/Game/")))
698 Prefixes.Add(Map.Package);
699 Prefixes.Add(Map.Package + TEXT(
"/"));
700 const FString Folder = FPackageName::GetLongPackagePath(Map.Package);
701 if (FPackageName::GetShortName(Folder).Equals(Map.Name, ESearchCase::IgnoreCase))
703 Prefixes.Add(Folder + TEXT(
"/"));
711 for (
const FString &Prefix : Prefixes)
713 if (Prefix.EndsWith(TEXT(
"/")) ? PackageName.StartsWith(Prefix) : PackageName.Equals(Prefix))
725 if (GEngine !=
nullptr)
727 for (
const FWorldContext &Context : GEngine->GetWorldContexts())
729 UWorld *World = Context.World();
730 if (World ==
nullptr)
734 const FString WorldPackage = World->GetOutermost()->GetName();
737 OutReason = FString::Printf(TEXT(
"its map '%s' is the current world"), *WorldPackage);
740 for (
const ULevelStreaming *Streaming : World->GetStreamingLevels())
742 if (Streaming !=
nullptr &&
745 OutReason = FString::Printf(TEXT(
"its level '%s' is streamed into the current world"),
746 *Streaming->GetWorldAssetPackageName());
758 TArray<FString> Alive;
759 for (TObjectIterator<UPackage> It; It; ++It)
761 const FString PackageName = It->GetName();
764 Alive.Add(PackageName);
765 if (Alive.Num() >= 5)
786 CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS,
true);
791 OutReason = FString::Printf(TEXT(
"objects from the pack are still loaded (%s%s)"),
792 *FString::Join(Alive, TEXT(
", ")), Alive.Num() >= 5 ? TEXT(
", ...") : TEXT(
""));
800 int32 Index = INDEX_NONE;
801 for (int32 i = 0; i <
Packs.Num(); ++i)
803 if (
Packs[i].GetName().Equals(Name, ESearchCase::IgnoreCase))
809 if (Index == INDEX_NONE)
811 OutError = FString::Printf(TEXT(
"unknown content pack '%s'"), *Name);
817 OutError = FString::Printf(TEXT(
"content pack '%s' is not mounted"), *Name);
823 OutError = FString::Printf(
824 TEXT(
"content pack '%s' was mounted by the engine at startup (Plugins/%s.upluginmanifest); "
825 "remove it from the Plugins directory and restart the server to unload it"),
835 OutError = FString::Printf(TEXT(
"cannot unmount content pack '%s': %s; load another map first"),
843 const FString MountPoint = FString::Printf(TEXT(
"/%s/"), *Pack.
GetName());
848 const auto BelongsToLiveWorld = [](
const UObject *Object)
850 const UWorld *World = Object->GetWorld();
851 return IsValid(World) && !World->bIsTearingDown;
853 for (TObjectIterator<ACarlaActorFactory> It; It; ++It)
855 if (
IsValid(*It) && BelongsToLiveWorld(*It))
857 It->ReleaseContentPack(MountPoint);
860 for (TObjectIterator<UActorDispatcher> It; It; ++It)
862 if (
IsValid(*It) && BelongsToLiveWorld(*It))
864 It->ReleaseContentPack(MountPoint);
873 UE_LOG(LogCarla, Warning,
874 TEXT(
"Content pack '%s': unmount refused after its blueprint definitions were dropped; "
875 "reload_world restores them"), *Pack.
GetName());
876 OutError = FString::Printf(TEXT(
"cannot unmount content pack '%s': %s; reload the world and retry"),
882 if (!IPluginManager::Get().UnmountExplicitlyLoadedPlugin(Pack.
GetName(), &FailReason))
884 OutError = FString::Printf(TEXT(
"cannot unmount plugin '%s': %s"),
885 *Pack.
GetName(), *FailReason.ToString());
892 UE_LOG(LogCarla, Log, TEXT(
"Content pack '%s' unmounted"), *Pack.
GetName());
static TArray< FString > UCarlaContentPackManager_LoadedPackages(const TArray< FString > &Prefixes)
static bool UCarlaContentPackManager_IsPackPackage(const TArray< FString > &Prefixes, const FString &PackageName)
static const TCHAR * CARLA_PACK_MANIFEST_NAME
static TArray< FString > UCarlaContentPackManager_PackagePrefixes(const FCarlaContentPack &Pack)
Package name prefixes that belong to a pack: its mount point plus, for every project map it ships in ...
static bool IsValid(const ACarlaWheeledVehicle *Vehicle)
Mounts CARLA content packs (content-only Unreal plugins cooked as DLC against a base release,...
bool MountPlugin(FCarlaContentPack &Pack, FString &OutError)
virtual void Initialize(FSubsystemCollectionBase &Collection) override
bool ArePackObjectsAlive(const FCarlaContentPack &Pack, FString &OutReason)
Objects under "/<Pack>/" survive a garbage collection (GC only runs when something from the pack was ...
bool Unmount(const FString &Name, FString &OutError)
Unmount a pack by name.
static UCarlaContentPackManager * Get()
virtual void Deinitialize() override
TArray< FString > GetPackContentDirs() const
Content dirs of all mounted packs, in mount order.
TArray< FString > GetPackMapNames() const
Map names declared by all mounted packs.
bool MountPaks(FCarlaContentPack &Pack, FString &OutError)
TArray< FCarlaContentPack > Packs
const FCarlaContentPack * FindPack(const FString &Name) const
bool Mount(const FString &Path, FString &OutError, const FCarlaContentPack **OutPack=nullptr)
Mount a pack.
FString FindMapPackage(const FString &MapName) const
Long package name of a map declared by a mounted pack, or empty.
void UnmountPaks(FCarlaContentPack &Pack)
FCarlaContentPackEvent OnPackMounted
void RegisterAssetRegistry(FCarlaContentPack &Pack)
static TArray< FString > FindPackCatalogFiles(const FString &RelativeName)
<ContentDir>/Config/<RelativeName> of every mounted pack where the file exists, in mount order (later...
static FString GetBaseReleaseStamp()
Base release stamp of this binary (<ProjectDir>/BaseRelease), empty in editor/dev builds.
bool CheckCompatibility(const FCarlaContentPackManifest &Manifest, FString &OutError) const
void Discover()
Mount every pack found in <ProjectDir>/Packs/*/carla-pack.json, the directories listed in -carla-pac...
TArray< FString > GetDiscoveryDirs() const
FCarlaContentPackEvent OnPackUnmounted
bool IsPackInUse(const FCarlaContentPack &Pack, FString &OutReason) const
The pack map or one of its levels is loaded in some world (no GC).
static void InvalidateMapCache()
Drop the cached .umap walk (content pack mounted/unmounted).
static bool LoadFromFile(const FString &InManifestPath, FCarlaContentPackManifest &OutManifest, FString &OutError)
Read and parse <ManifestPath>. Fills ManifestPath/PackDir.
FString PackDir
Absolute path of the pack directory (parent of the manifest).
TArray< FCarlaContentPackMap > Maps
TArray< FString > GetMapNames() const
One map declared by a content pack manifest.
A content pack known to the manager (discovered or mounted explicitly).
FString ContentDir
Absolute path of the plugin content dir (<PackDir>/Content), the filesystem side of the "/<Name>/" mo...
FString PluginFile
Absolute path of <PackDir>/<Name>.uplugin.
bool bEngineMounted
True when the plugin was mounted by the engine at startup (a pack dropped under Plugins/ with a ....
const FString & GetName() const
FString PackDir
Absolute pack directory (contains carla-pack.json and <Name>.uplugin).
FCarlaContentPackManifest Manifest
TArray< FString > MountedPaks
Pak files mounted for this pack (cooked builds only).