CARLA
Loading...
Searching...
No Matches
ActorDispatcher.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"
13#include "Carla/Game/Tagger.h"
15
16#include <util/ue-header-guard-begin.h>
17#include "GameFramework/Controller.h"
18#ifdef WITH_ROS2
20 #include "carla/ros2/ROS2.h"
22 #include <variant>
23#endif
24#include <util/ue-header-guard-end.h>
25
27{
29 {
30 Definition.UId = static_cast<uint32>(SpawnFunctions.Num()) + 1u;
31 Definitions.Emplace(Definition);
32 SpawnFunctions.Emplace(Functor);
33 Classes.Add(Definition.Id, Definition.Class);
34 }
35 else
36 {
37 UE_LOG(LogCarla, Warning, TEXT("Invalid definition '%s' ignored"), *Definition.Id);
38 }
39}
40
41void UActorDispatcher::ReleaseContentPack(const FString &MountPoint)
42{
43 // Drop the definitions outright instead of only clearing their class: the
44 // blueprint library the client asks for is built from Definitions, so a
45 // definition left behind keeps advertising a blueprint whose content is no
46 // longer mounted until the next world load rebuilds the list.
47 Definitions.RemoveAll([&MountPoint](const FActorDefinition &Definition)
48 {
49 return ACarlaActorFactory::DefinitionReferencesPath(Definition, MountPoint);
50 });
51 for (auto It = Classes.CreateIterator(); It; ++It)
52 {
53 const bool bStillDefined = Definitions.ContainsByPredicate(
54 [&It](const FActorDefinition &Definition) { return Definition.Id == It.Key(); });
55 if (!bStillDefined)
56 {
57 It.RemoveCurrent();
58 }
59 }
60}
61
63{
64 for (const auto &Definition : ActorFactory.GetDefinitions())
65 {
66 const bool bAlreadyBound = Definitions.ContainsByPredicate(
67 [&Definition](const FActorDefinition &Existing) { return Existing.Id == Definition.Id; });
68 if (bAlreadyBound)
69 {
70 continue;
71 }
72 Bind(Definition, [&](const FTransform &Transform, const FActorDescription &Description) {
73 return ActorFactory.SpawnActor(Transform, Description);
74 });
75 }
76}
77
79{
80 for (const auto &Definition : ActorFactory.GetDefinitions())
81 {
82 Bind(Definition, [&](const FTransform &Transform, const FActorDescription &Description) {
83 return ActorFactory.SpawnActor(Transform, Description);
84 });
85 }
86}
87
88TPair<EActorSpawnResultStatus, FCarlaActor*> UActorDispatcher::SpawnActor(
89 const FTransform &Transform,
90 FActorDescription Description,
91 FCarlaActor::IdType DesiredId)
92{
93 if ((Description.UId == 0u) || (Description.UId > static_cast<uint32>(SpawnFunctions.Num())))
94 {
95 UE_LOG(LogCarla, Error, TEXT("Invalid ActorDescription '%s' (UId=%d)"), *Description.Id, Description.UId);
96 return MakeTuple(EActorSpawnResultStatus::InvalidDescription, nullptr);
97 }
98
99 UE_LOG(LogCarla, Log, TEXT("Spawning actor '%s'"), *Description.Id);
100
101 auto Class = Classes.Find(Description.Id);
102 if (Class == nullptr)
103 {
104 UE_LOG(LogCarla, Error, TEXT("Invalid ActorDescription '%s' (UId=%d)"), *Description.Id, Description.UId);
105 return MakeTuple(EActorSpawnResultStatus::InvalidDescription, nullptr);
106 }
107
108 Description.Class = *Class;
109 FActorSpawnResult Result = SpawnFunctions[Description.UId - 1](Transform, Description);
110
111 if ((Result.Status == EActorSpawnResultStatus::Success) && (Result.Actor == nullptr))
112 {
113 UE_LOG(LogCarla, Warning, TEXT("ActorSpawnResult: Trying to spawn '%s'"), *Description.Id);
114 UE_LOG(LogCarla, Warning, TEXT("ActorSpawnResult: Reported success but did not return an actor"));
115 Result.Status = EActorSpawnResultStatus::UnknownError;
116 }
117
118 if (Result.IsValid())
119 {
120 // Tag before registering: FActorRegistry::MakeCarlaActor snapshots the
121 // semantic tags into the actor info that spawn_actor/get_actors return,
122 // and the OnActorSpawned tagger delegate already ran before the factory
123 // assigned the mesh (props were tagged "None" there).
124 ATagger::TagActor(*Result.Actor, true);
125 }
126
127 FCarlaActor* View = Result.IsValid() ?
128 RegisterActor(*Result.Actor, std::move(Description), DesiredId) : nullptr;
129 if (!View)
130 {
131 UE_LOG(LogCarla, Warning, TEXT("Failed to spawn actor '%s'"), *Description.Id);
132 check(Result.Status != EActorSpawnResultStatus::Success);
133 }
134
135 return MakeTuple(Result.Status, View);
136}
137
139 const FTransform &Transform,
140 FActorDescription Description)
141{
142 if ((Description.UId == 0u) || (Description.UId > static_cast<uint32>(SpawnFunctions.Num())))
143 {
144 UE_LOG(LogCarla, Error, TEXT("Invalid ActorDescription '%s' (UId=%d)"), *Description.Id, Description.UId);
145 return nullptr;
146 }
147
148 UE_LOG(LogCarla, Log, TEXT("Spawning actor '%s'"), *Description.Id);
149
150 auto Class = Classes.Find(Description.Id);
151 if (Class == nullptr)
152 {
153 UE_LOG(LogCarla, Error, TEXT("Invalid ActorDescription '%s' (UId=%d)"), *Description.Id, Description.UId);
154 return nullptr;
155 }
156
157 Description.Class = *Class;
158 FActorSpawnResult Result = SpawnFunctions[Description.UId - 1](Transform, Description);
159
160 if ((Result.Status == EActorSpawnResultStatus::Success) && (Result.Actor == nullptr))
161 {
162 UE_LOG(LogCarla, Warning, TEXT("ActorSpawnResult: Trying to spawn '%s'"), *Description.Id);
163 UE_LOG(LogCarla, Warning, TEXT("ActorSpawnResult: Reported success but did not return an actor"));
164 Result.Status = EActorSpawnResultStatus::UnknownError;
165 return nullptr;
166 }
167
168 if (Result.Status == EActorSpawnResultStatus::Success)
169 {
170 return Result.Actor;
171 }
172
173 return nullptr;
174}
175
177{
178 // Check if the actor is in the registry.
179 FCarlaActor* View = Registry.FindCarlaActor(ActorId);
180
181 // Invalid destruction if is not marked to PendingKill (except is dormant, dormant actors can be destroyed)
182 if (!View)
183 {
184 UE_LOG(LogCarla, Warning, TEXT("Trying to destroy actor that is not in the registry"));
185 return false;
186 }
187
188 const FString &Id = View->GetActorInfo()->Description.Id;
189
190 // Destroy its controller if present.
191 AActor* Actor = View->GetActor();
192 if(Actor)
193 {
194 APawn* Pawn = Cast<APawn>(Actor);
195 AController* Controller = (Pawn != nullptr ? Pawn->GetController() : nullptr);
196 if (Controller != nullptr)
197 {
198 UE_LOG(LogCarla, Log, TEXT("Destroying actor's controller: '%s'"), *Id);
199 bool Success = Controller->Destroy();
200 if (!Success)
201 {
202 UE_LOG(LogCarla, Error, TEXT("Failed to destroy actor's controller: '%s'"), *Id);
203 }
204 }
205
206 // Destroy the actor.
207 UE_LOG(LogCarla, Log, TEXT("UActorDispatcher::Destroying actor: '%s' %x"), *Id, Actor);
208 UE_LOG(LogCarla, Log, TEXT(" %s"), Actor?*Actor->GetName():*FString("None"));
209 if (!Actor || !Actor->Destroy())
210 {
211 UE_LOG(LogCarla, Error, TEXT("Failed to destroy actor: '%s'"), *Id);
212 return false;
213 }
214 }
215
216 Registry.Deregister(ActorId);
217
218 return true;
219}
220
222 AActor &Actor, FActorDescription Description,
223 FActorRegistry::IdType DesiredId)
224{
225 FCarlaActor* View = Registry.Register(Actor, Description, DesiredId);
226 if (View)
227 {
228 // TODO: support external actor destruction
229 Actor.OnDestroyed.AddDynamic(this, &UActorDispatcher::OnActorDestroyed);
230
231 // ROS2 mapping of actor->ros_name
232 #ifdef WITH_ROS2
233 auto ROS2 = carla::ros2::ROS2::GetInstance();
234 if (ROS2->IsEnabled())
235 {
236 // actor ros_name
237 std::string RosName;
238 std::string RosTopicName;
239 for (auto &&Attr : Description.Variations)
240 {
241 if (Attr.Key == "ros_name")
242 {
243 RosName = std::string(TCHAR_TO_UTF8(*Attr.Value.Value));
244 }
245 if (Attr.Key == "ros_topic_name")
246 {
247 RosTopicName = std::string(TCHAR_TO_UTF8(*Attr.Value.Value));
248 }
249 }
250 const std::string id = std::string(TCHAR_TO_UTF8(*Description.Id));
251 std::string ResolvedRosName;
252 if (RosName == id) {
253 if(RosName.find("vehicle") != std::string::npos)
254 {
255 ResolvedRosName = "vehicle" + std::to_string(View->GetActorId());
256 }
257 else
258 {
259 size_t pos = RosName.find_last_of('.');
260 if (pos != std::string::npos) {
261 ResolvedRosName = RosName.substr(pos + 1) + "__";
262 }
263 }
264 } else {
265 ResolvedRosName = RosName;
266 }
267 if (!ResolvedRosName.empty())
268 {
269 ROS2->RegisterSensor(static_cast<void*>(&Actor), ResolvedRosName, ResolvedRosName, true);
270 }
271 // exact-topic override (empty when the attribute is unset or equals the
272 // blueprint id: signal to generate the default topic name)
273 ROS2->AddActorRosTopicName(static_cast<void*>(&Actor), RosTopicName == id ? "" : RosTopicName);
274
275 // vehicle controller for hero. Scan the variations once for the hero role and the
276 // opt-in Ackermann/Autoware control flags; the control topics are mutually
277 // exclusive on the ROS 2 side, so only request them when an attribute asks for it.
278 bool bIsHero = false;
279 bool bEnableAckermannControl = false;
280 bool bEnableAutowareControl = false;
281 for (auto &&Attr : Description.Variations)
282 {
283 if (Attr.Key == "role_name" && (Attr.Value.Value == "hero" || Attr.Value.Value == "ego"))
284 {
285 bIsHero = true;
286 }
287 else if (Attr.Key == "ros2_ackermann_control")
288 {
289 bEnableAckermannControl = Attr.Value.Value.ToBool();
290 }
291 else if (Attr.Key == "ros2_autoware_control")
292 {
293 bEnableAutowareControl = Attr.Value.Value.ToBool();
294 }
295 }
296 if (bIsHero)
297 {
298 if (bEnableAutowareControl)
299 {
300 // Autoware maps steering 1:1 to wheel angle; a speed-dependent
301 // steering curve would silently rescale the command (tier4 port).
303 }
304 ROS2->RegisterVehicle(static_cast<void*>(&Actor), ResolvedRosName, ResolvedRosName, [ResolvedRosName](void *Actor, carla::ros2::ROS2CallbackData Data) -> void
305 {
306 AActor *UEActor = reinterpret_cast<AActor *>(Actor);
307 ActorROS2Handler Handler(UEActor, ResolvedRosName);
308 std::visit(Handler, Data);
309 }, bEnableAckermannControl, bEnableAutowareControl);
310 #if defined(WITH_ROS2_DEMO)
311 ROS2->AddBasicSubscriberCallback(static_cast<void*>(&Actor), ResolvedRosName, [ResolvedRosName](void *Actor, carla::ros2::ROS2MessageCallbackData Data) -> void
312 {
313 AActor *UEActor = reinterpret_cast<AActor *>(Actor);
314 ActorROS2Handler Handler(UEActor, ResolvedRosName);
315 std::visit(Handler, Data);
316 });
317 #endif
318 }
319 }
320 #endif
321 }
322 return View;
323}
324
326{
327 Registry.PutActorToSleep(Id, CarlaEpisode);
328}
329
331{
332 Registry.WakeActorUp(Id, CarlaEpisode);
333}
334
336{
337 FCarlaActor* CarlaActor = Registry.FindCarlaActor(Actor);
338
339 #ifdef WITH_ROS2
340 auto ROS2 = carla::ros2::ROS2::GetInstance();
341 if (ROS2->IsEnabled())
342 {
343 void *ActorKey = reinterpret_cast<void *>(Actor);
344 // UnregisterVehicle clears the per-actor subscribers, the actor-callback
345 // map, and the ros_name maps in a single step. The call is idempotent on
346 // a missing key, so it works for both vehicles and sensors that never
347 // registered a callback.
348 ROS2->UnregisterVehicle(ActorKey);
349 ROS2->RemoveActorRosTopicName(ActorKey);
350 #if defined(WITH_ROS2_DEMO)
351 ROS2->RemoveBasicSubscriberCallback(ActorKey);
352 #endif
353 }
354 #endif
355
356 if (CarlaActor && CarlaActor->IsActive())
357 {
358 // An actor can also go without the episode destroying it (a life span
359 // that ran out, falling out of the world): Multi-GPU secondaries mirror it
360 // and must drop it too. FFrameData ignores a second report of the same id.
361 UCarlaEpisode *Episode = Cast<UCarlaEpisode>(GetOuter());
362 if (Episode && Episode->bIsPrimaryServer)
363 {
364 Episode->GetFrameData().AddEvent(CarlaRecorderEventDel{CarlaActor->GetActorId()});
365 }
366 Registry.Deregister(CarlaActor->GetActorId());
367 }
368}
Base class for Carla actor factories.
virtual TArray< FActorDefinition > GetDefinitions()
Retrieve the list of actor definitions that this class is able to spawn.
virtual FActorSpawnResult SpawnActor(const FTransform &SpawnAtTransform, const FActorDescription &ActorDescription)
Spawn an actor based on ActorDescription and Transform.
static bool DefinitionReferencesPath(const FActorDefinition &Definition, const FString &MountPoint)
True when the definition's class or any attribute/variation value points at content under MountPoint.
static void TagActor(const AActor &Actor, bool bTagForSemanticSegmentation)
Set the tag of an actor.
Definition Tagger.cpp:130
static bool FlattenSteeringCurve(AActor *Actor)
Flatten the vehicle's steering curve to 1.0 at all speeds so the Autoware steering command maps 1:1 t...
FCarlaActor::IdType IdType
FCarlaActor * Register(AActor &Actor, FActorDescription Description, IdType DesiredId=0)
Register the Actor in the database.
void Deregister(IdType Id)
FCarlaActor * FindCarlaActor(IdType Id)
void PutActorToSleep(IdType Id, UCarlaEpisode *CarlaEpisode)
void WakeActorUp(IdType Id, UCarlaEpisode *CarlaEpisode)
A view over an actor and its properties.
Definition CarlaActor.h:25
AActor * GetActor()
Definition CarlaActor.h:91
uint32 IdType
Definition CarlaActor.h:28
bool IsActive() const
Definition CarlaActor.h:66
const FActorInfo * GetActorInfo() const
Definition CarlaActor.h:101
IdType GetActorId() const
Definition CarlaActor.h:81
void AddEvent(const CarlaRecorderEventAdd &Event)
static bool CheckActorDefinition(const FActorDefinition &ActorDefinitions)
Return whether the actor definition is valid. Prints all the errors found.
void BindNewDefinitions(ACarlaActorFactory &ActorFactory)
Bind the definitions of ActorFactory that are not bound yet.
void OnActorDestroyed(AActor *Actor)
TFunction< FActorSpawnResult(const FTransform &, const FActorDescription &)> SpawnFunctionType
void Bind(FActorDefinition Definition, SpawnFunctionType SpawnFunction)
Bind a definition to a spawn function.
void PutActorToSleep(FCarlaActor::IdType Id, UCarlaEpisode *CarlaEpisode)
TMap< FString, TSubclassOf< AActor > > Classes
FActorRegistry Registry
void WakeActorUp(FCarlaActor::IdType Id, UCarlaEpisode *CarlaEpisode)
FCarlaActor * RegisterActor(AActor &Actor, FActorDescription ActorDescription, FActorRegistry::IdType DesiredId=0)
Register an actor that was not created using "SpawnActor" function but that should be kept in the reg...
bool DestroyActor(FCarlaActor::IdType ActorId)
Destroys an actor, properly removing it from the registry.
AActor * ReSpawnActor(const FTransform &Transform, FActorDescription ActorDescription)
ReSpawns an actor based on ActorDescription at Transform.
TPair< EActorSpawnResultStatus, FCarlaActor * > SpawnActor(const FTransform &Transform, FActorDescription ActorDescription, FCarlaActor::IdType DesiredId=0)
Spawns an actor based on ActorDescription at Transform.
TArray< FActorDefinition > Definitions
UPROPERTY so the garbage collector clears the class references when a content pack's classes are dest...
TArray< SpawnFunctionType > SpawnFunctions
void ReleaseContentPack(const FString &MountPoint)
Forget the classes of every definition that refers to content under MountPoint (a content pack being ...
A simulation episode.
FFrameData & GetFrameData()
static std::shared_ptr< ROS2 > GetInstance()
Definition ROS2.h:61
std::variant< MessageControl > ROS2MessageCallbackData
std::variant< VehicleControl, AckermannControl, VehicleAccelerationControl > ROS2CallbackData
A definition of a Carla Actor with all the variation and attributes.
uint32 UId
Uniquely identifies the definition (no need to fill it).
TSubclassOf< AActor > Class
Class of the actor to be spawned (Optional).
A description of a Carla Actor with all its variation.
TMap< FString, FActorAttribute > Variations
User selected variations of the actor.
TSubclassOf< AActor > Class
Class of the actor to be spawned.
uint32 UId
UId of the definition in which this description was based.
FActorDescription Description
Definition ActorInfo.h:21
Result of an actor spawn function.
EActorSpawnResultStatus Status