CARLA
Loading...
Searching...
No Matches
CarlaStatics.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma
2// de Barcelona (UAB).
3//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
8#include "Carla.h"
10
11#include <util/ue-header-guard-begin.h>
12#include "AssetRegistry/ARFilter.h"
13#include "AssetRegistry/AssetData.h"
14#include "AssetRegistry/IAssetRegistry.h"
15#include "Engine/World.h"
16#include "Interfaces/IPluginManager.h"
17#include "Misc/Paths.h"
18#include "Misc/PackageName.h"
19#include "Modules/ModuleManager.h"
20#include "HAL/FileManagerGeneric.h"
21#include <util/ue-header-guard-end.h>
22
23
24/// UWorld assets the asset registry knows outside /Engine (project content,
25/// mounted plugins, content packs). Cooked base maps live inside Pak/IoStore
26/// containers where the .umap walk finds nothing; the premade
27/// AssetRegistry.bin of a packaged build still lists them. The registry
28/// asserts on non-game threads, so off-thread callers get an empty list.
29static TArray<FAssetData> UCarlaStatics_GetRegistryWorlds()
30{
31 TArray<FAssetData> Worlds;
32 if (!IsInGameThread())
33 {
34 return Worlds;
35 }
36 IAssetRegistry *AssetRegistry = IAssetRegistry::Get();
37 if (AssetRegistry == nullptr)
38 {
39 return Worlds;
40 }
41 FARFilter Filter;
42 Filter.ClassPaths.Add(UWorld::StaticClass()->GetClassPathName());
43 AssetRegistry->GetAssets(Filter, Worlds);
44 // Only project content and project plugins (incl. mounted content packs) hold
45 // CARLA maps; engine content and engine plugins (PCG/Water/Volumetrics sample
46 // levels, ...) never do.
47 TSet<FString> Roots;
48 Roots.Add(TEXT("/Game/"));
49 for (const TSharedRef<IPlugin> &Plugin : IPluginManager::Get().GetEnabledPluginsWithContent())
50 {
51 if (Plugin->GetLoadedFrom() != EPluginLoadedFrom::Engine)
52 {
53 Roots.Add(FString::Printf(TEXT("/%s/"), *Plugin->GetName()));
54 }
55 }
56 // A cooked World Partition town also has every generated streaming cell
57 // (<Town>/_Generated_/<Cell>) registered as a UWorld named after the town;
58 // those are not loadable maps. Registry rows whose package no longer exists
59 // (stale cache entries, unmounted packs) are dropped too.
60 Worlds.RemoveAll([&Roots](const FAssetData &World)
61 {
62 const FString PackageName = World.PackageName.ToString();
63 int32 SecondSlash = INDEX_NONE;
64 const bool bHasRoot = PackageName.FindChar(TEXT('/'), SecondSlash) &&
65 PackageName.Len() > 1 && PackageName.RightChop(1).FindChar(TEXT('/'), SecondSlash);
66 const FString Root = bHasRoot ? PackageName.Left(SecondSlash + 2) : FString();
67 return !Roots.Contains(Root) ||
68 (World.PackageFlags & PKG_CookGenerated) != 0 ||
69 PackageName.Contains(TEXT("/_Generated_/")) ||
70 !FPackageName::DoesPackageExist(PackageName);
71 });
72 return Worlds;
73}
74
75/// The recursive .umap walk over the project and plugin content dirs is
76/// expensive (tens of thousands of files in an editor tree) and runs on the
77/// game thread for get_available_maps / load_world, so its result is cached
78/// keyed on the set of content dirs; content pack mount/unmount invalidates it.
79static TArray<FString> UCarlaStatics_CachedMapFiles;
82
83static const TArray<FString> &UCarlaStatics_GetMapFiles()
84{
85 TArray<FString> PathList;
86 PathList.Add(FPaths::ProjectContentDir());
88 const FString Key = FString::Join(PathList, TEXT(";"));
90 {
92 }
94 for (const FString &Path : PathList)
95 {
96 if (FPaths::DirectoryExists(Path))
97 {
98 UE_LOG(LogCarla, Log, TEXT("Path: %s"), *Path);
99 IFileManager::Get().FindFilesRecursive(UCarlaStatics_CachedMapFiles, *Path, TEXT("*.umap"), true, false, false);
100 }
101 }
105}
106
111
113{
114 TArray<FString> OutContentDirs;
115 const TArray<TSharedRef<IPlugin>> Plugins = IPluginManager::Get().GetDiscoveredPlugins();
116 for (const TSharedRef<IPlugin>& Plugin : Plugins)
117 {
118 if (Plugin->GetLoadedFrom() == EPluginLoadedFrom::Engine)
119 {
120 continue;
121 }
122 // Content of a plugin that is not enabled (never enabled, or a content
123 // pack that was unmounted) has no mount point and cannot be loaded.
124 if (!Plugin->IsEnabled())
125 {
126 continue;
127 }
128
129 FString ContentDir = Plugin->GetContentDir();
130 if (FPaths::DirectoryExists(ContentDir))
131 {
132 OutContentDirs.Add(ContentDir);
133 }
134 }
135 return OutContentDirs;
136}
137
138
139/// Maps that get_available_maps must not list: the OpenDRIVE-generated
140/// scratch map, sub-levels and World Partition helper packages, and the test
141/// maps — except EmptyMap, which is a supported empty sandbox (it ships with
142/// an empty OpenDRIVE file so get_map() works).
143static bool UCarlaStatics_IsHiddenMap(const FString& PathOrPackage)
144{
145 if (PathOrPackage.Contains("TestMaps"))
146 {
147 return FPaths::GetBaseFilename(PathOrPackage) != TEXT("EmptyMap");
148 }
149 if (PathOrPackage.Contains("OpenDriveMap") || PathOrPackage.Contains("Sublevels") ||
150 PathOrPackage.Contains("/BaseMap/") || PathOrPackage.Contains("/BaseLargeMap/") ||
151 PathOrPackage.Contains("_Tile_"))
152 {
153 return true;
154 }
155
156 // A map lives either directly in a Maps folder or in a folder of its own
157 // name; anything else is a sublevel (Town15_Road) or an editor helper asset.
158 const FString Base = FPaths::GetBaseFilename(PathOrPackage);
159 const FString Parent = FPaths::GetCleanFilename(FPaths::GetPath(PathOrPackage));
160 return Parent != TEXT("Maps") && Parent != Base;
161}
162
164{
165 TArray<FString> MapNameList = UCarlaStatics_GetMapFiles();
166
167 // Filter out undesired maps
168 MapNameList.RemoveAll([](const FString& Name) {
169 return UCarlaStatics_IsHiddenMap(Name);
170 });
171
172 for (int i = 0; i < MapNameList.Num(); i++) {
173 MapNameList[i] = FPaths::GetBaseFilename(*MapNameList[i]);
174 }
175
176 // Cooked base maps: see UCarlaStatics_GetRegistryWorlds.
177 {
178 for (const FAssetData &World : UCarlaStatics_GetRegistryWorlds())
179 {
180 const FString PackageName = World.PackageName.ToString();
181 if (UCarlaStatics_IsHiddenMap(PackageName))
182 {
183 continue;
184 }
185 MapNameList.AddUnique(World.AssetName.ToString());
186 }
187 }
188
189 // Maps declared by mounted content packs (manifest is authoritative).
191 {
192 for (const FString &PackMap : ContentPacks->GetPackMapNames())
193 {
194 MapNameList.AddUnique(PackMap);
195 }
196 }
197
198 return MapNameList;
199}
200
201FString UCarlaStatics::FindMapPath(const FString &MapName)
202{
203 if (MapName.IsEmpty())
204 {
205 return FString();
206 }
207
208 // Look for matching map files
209 for (const FString& FilePath : UCarlaStatics_GetMapFiles())
210 {
211 FString FileName = FPaths::GetBaseFilename(FilePath); // just "MyMap", no path, no extension
212 if (FileName.Equals(MapName, ESearchCase::IgnoreCase))
213 {
214 return FilePath; // Return the full path of the first matching map. Only one map is expected.
215 }
216 }
217
218 // Cooked packages can live exclusively in Pak/IoStore containers, where
219 // IFileManager cannot discover their .umap files. CARLA's generated
220 // OpenDRIVE world is a project map at this canonical package path.
221 const FString ProjectMapPackage = FString::Printf(
222 TEXT("/Game/Carla/Maps/%s"), *MapName);
223 if (FPackageName::DoesPackageExist(ProjectMapPackage))
224 {
225 return ProjectMapPackage;
226 }
227
228 // Cooked base maps outside the canonical folder (World Partition towns at
229 // /Game/Carla/Maps/<Name>/<Name>, /Game/Carla/Maps/TestMaps/EmptyMap) are
230 // only known to the asset registry. Prefer the canonical WP package over
231 // any other same-named world.
232 {
233 const TArray<FAssetData> Worlds = UCarlaStatics_GetRegistryWorlds();
234 const FString CanonicalWP = FString::Printf(TEXT("/Game/Carla/Maps/%s/%s"), *MapName, *MapName);
235 FString FirstMatch;
236 for (const FAssetData &World : Worlds)
237 {
238 if (!World.AssetName.ToString().Equals(MapName, ESearchCase::IgnoreCase))
239 {
240 continue;
241 }
242 const FString PackageName = World.PackageName.ToString();
243 if (PackageName.Equals(CanonicalWP, ESearchCase::IgnoreCase))
244 {
245 return PackageName;
246 }
247 if (FirstMatch.IsEmpty())
248 {
249 FirstMatch = PackageName;
250 }
251 }
252 if (!FirstMatch.IsEmpty())
253 {
254 return FirstMatch;
255 }
256 }
257
258 // Maps of mounted content packs resolve to their manifest package path,
259 // e.g. /NewPack/Maps/NewTown. Base maps win over a pack map of the same name.
261 {
262 const FString PackMapPackage = ContentPacks->FindMapPackage(MapName);
263 if (!PackMapPackage.IsEmpty())
264 {
265 return PackMapPackage;
266 }
267 }
268
269 return FString();
270}
static const TArray< FString > & UCarlaStatics_GetMapFiles()
static bool UCarlaStatics_IsHiddenMap(const FString &PathOrPackage)
Maps that get_available_maps must not list: the OpenDRIVE-generated scratch map, sub-levels and World...
static TArray< FAssetData > UCarlaStatics_GetRegistryWorlds()
UWorld assets the asset registry knows outside /Engine (project content, mounted plugins,...
static FString UCarlaStatics_CachedMapFilesKey
static bool UCarlaStatics_bMapFilesDirty
static TArray< FString > UCarlaStatics_CachedMapFiles
The recursive .umap walk over the project and plugin content dirs is expensive (tens of thousands of ...
Mounts CARLA content packs (content-only Unreal plugins cooked as DLC against a base release,...
static UCarlaContentPackManager * Get()
static FString FindMapPath(const FString &MapName)
static void InvalidateMapCache()
Drop the cached .umap walk (content pack mounted/unmounted).
static TArray< FString > GetAllPluginContentPaths()
static TArray< FString > GetAllMapNames()