CARLA
Loading...
Searching...
No Matches
CarlaEngine.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"
23#include "Carla/Sensor/Sensor.h"
24
26#include <carla/Logging.h>
27#include <carla/MsgPack.h>
33#include <carla/ros2/ROS2.h>
40
41#include <util/ue-header-guard-begin.h>
42#include "Engine/Engine.h"
43#include "Engine/GameViewportClient.h"
44#include "EngineUtils.h"
45#include "GameFramework/WorldSettings.h"
46#include "HAL/FileManager.h"
47#include "HAL/IConsoleManager.h"
48#include "HAL/PlatformProcess.h"
49#include "HAL/PlatformTime.h"
50#include "Misc/App.h"
51#include "Misc/FileHelper.h"
52#include "Misc/Paths.h"
53#include "PhysicsEngine/PhysicsSettings.h"
54#include "SceneInterface.h"
55#include "Engine/Engine.h"
56#include "Engine/GameViewportClient.h"
57#include <util/ue-header-guard-end.h>
58
59#include <cstdlib>
60#include <cstring>
61#include <optional>
62#include <thread>
63
64// =============================================================================
65// -- Static local methods -----------------------------------------------------
66// =============================================================================
67
68// init static variables
70
72{
73 return std::max(std::thread::hardware_concurrency(), 4u) - 2u;
74}
75
76#if !UE_BUILD_SHIPPING
77static TAutoConsoleVariable<int32> CVarCarlaMultiGpuVerifyFrames(
78 TEXT("carla.MultiGpu.VerifyFrames"),
79 1,
80 TEXT("Multi-GPU primary: read every frame sent to the secondaries back and log an error when it ")
81 TEXT("does not parse completely with this build (1 = on, 0 = off)."),
82 ECVF_Default);
83#endif // !UE_BUILD_SHIPPING
84
85static TOptional<double> FCarlaEngine_GetFixedDeltaSeconds()
86{
87 return FApp::IsBenchmarking() ? FApp::GetFixedDeltaTime() : TOptional<double>{};
88}
89
90static void FCarlaEngine_SetFixedDeltaSeconds(TOptional<double> FixedDeltaSeconds)
91{
92 FApp::SetBenchmarking(FixedDeltaSeconds.IsSet());
93 FApp::SetFixedDeltaTime(FixedDeltaSeconds.Get(0.0));
94}
95
96/// Whether a travel to another map is scheduled for @a World: load_world()
97/// only schedules it, and the engine performs it at the start of the next
98/// engine frame, before ticking any world.
99static bool FCarlaEngine_IsWorldTravelPending(const UWorld *World)
100{
101 const FWorldContext *Context = (GEngine && World) ? GEngine->GetWorldContextFromWorld(World) : nullptr;
102 return (Context != nullptr) && !Context->TravelURL.IsEmpty();
103}
104
121
122/// Multi-GPU secondary: the world the primary generated from OpenDRIVE, where
123/// this server's OpenDriveMap reads it and with the same parameters, before
124/// the map opens; for any other map, no generated world of an earlier load.
126{
127 const FString XODRPath = UOpenDrive::GetSavedXODRPath(TEXT("OpenDriveMap"));
128 IFileManager::Get().Delete(*XODRPath, false, true, true);
129 if (!Extras.opendrive)
130 {
131 return;
132 }
133 TArray<uint8> OpenDrive;
134 OpenDrive.Append(reinterpret_cast<const uint8 *>(Extras.opendrive->data()),
135 static_cast<int32>(Extras.opendrive->size()));
136 if (!FFileHelper::SaveArrayToFile(OpenDrive, *XODRPath))
137 {
138 UE_LOG(LogCarla, Error, TEXT("Multi-GPU: cannot write the primary's OpenDRIVE world to %s"), *XODRPath);
139 return;
140 }
142 if ((GameInstance == nullptr) || !Extras.generation_parameters)
143 {
144 return;
145 }
146 try
147 {
149 carla::MsgPack::UnPack<carla::rpc::OpendriveGenerationParameters>(
150 reinterpret_cast<const unsigned char *>(Extras.generation_parameters->data()),
151 Extras.generation_parameters->size()));
152 }
153 catch (const std::exception &e)
154 {
155 UE_LOG(LogCarla, Error, TEXT("Multi-GPU: the primary's OpenDRIVE generation parameters do not read: %s"),
156 UTF8_TO_TCHAR(e.what()));
157 }
158}
159
160// =============================================================================
161// -- FCarlaEngine -------------------------------------------------------------
162// =============================================================================
163
165{
166 // The command callback captures this engine and outlives it otherwise
167 // (Secondary is kept alive by its own commander).
168 if (Secondary)
169 {
170 Secondary->Stop();
171 // OnBeginFrame took over the process-wide time step; give it back (the
172 // editor keeps running after a PIE secondary ends).
173 FApp::SetUseFixedTimeStep(false);
174 // The generated worlds the primary sent are of no use to anyone else.
175 IFileManager::Get().DeleteDirectory(
176 *FPaths::GetPath(UOpenDrive::GetSavedXODRPath(TEXT("OpenDriveMap"))), false, true);
178 }
179 if (bIsRunning)
180 {
181 #if defined(WITH_ROS2)
182 auto ROS2 = carla::ros2::ROS2::GetInstance();
183 if (ROS2->IsEnabled())
184 ROS2->Shutdown();
185 #endif
186 FWorldDelegates::OnWorldTickStart.Remove(OnPreTickHandle);
187 FWorldDelegates::OnWorldPostActorTick.Remove(OnPostTickHandle);
189 FCoreDelegates::OnBeginFrame.Remove(OnBeginFrameHandle);
190 if (GEngine)
191 {
192 GEngine->OnTravelFailure().Remove(OnTravelFailureHandle);
193 }
194 FWorldDelegates::OnPreWorldInitialization.Remove(OnPreWorldInitializationHandle);
195 }
196}
197
199{
200 TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__);
201 if (!bIsRunning)
202 {
203 // Content packs are discovered when the engine subsystem initializes;
204 // this is the no-op safety net that guarantees they are mounted before
205 // the RPC server answers its first get_available_maps.
207 {
208 ContentPacks->Discover();
209 }
210 // A previous run's generated OpenDRIVE files must not shadow the cooked
211 // OpenDriveMap stub in this one. A Multi-GPU secondary keeps the worlds
212 // the primary sends in a folder of its own and leaves the shared files
213 // alone: the primary may run from the same installation.
214 if (Settings.PrimaryIP.empty())
215 {
217 }
218 else
219 {
220 // Unique to this process even across PID namespaces (containers that
221 // share an installation).
222 UOpenDrive::SetSavedXODRDirectory(FPaths::ProjectSavedDir() / FString::Printf(
223 TEXT("OpenDrive/MultiGpuSecondary-%s"), *FApp::GetInstanceId().ToString()));
224 }
225
226 const auto StreamingPort = Settings.StreamingPort;
227 const auto SecondaryPort = Settings.SecondaryPort;
228 const auto PrimaryIP = Settings.PrimaryIP;
229 const auto PrimaryPort = Settings.PrimaryPort;
230
231 // rpclib's asio acceptor throws (std::system_error, "address already
232 // in use") when the RPC port is taken -- typically another CARLA
233 // instance still running. Unhandled it aborts the whole editor with
234 // SIGABRT and a core dump; fail with a readable message instead.
235 try
236 {
237 auto BroadcastStream = Server.Start(Settings.RPCPort, StreamingPort, SecondaryPort);
239 WorldObserver.SetStream(BroadcastStream);
240 }
241 catch (const std::exception &e)
242 {
243 UE_LOG(LogCarla, Error,
244 TEXT("CARLA server failed to start on RPC port %d: %s. "
245 "Another CARLA server is probably running on this port -- "
246 "close it or launch with a different -carla-rpc-port. "
247 "Shutting down."),
248 Settings.RPCPort, UTF8_TO_TCHAR(e.what()));
249 // Immediate exit: a deferred RequestExit lets the engine keep
250 // initializing this half-built game instance and it segfaults in
251 // teardown before ever reaching the main loop. The UE log mirror
252 // may not flush before _exit, so also print straight to stderr.
253 fprintf(stderr,
254 "ERROR: CARLA server failed to start on RPC port %d: %s. "
255 "Another CARLA server is probably running on this port -- "
256 "close it or launch with a different -carla-rpc-port.\n",
257 static_cast<int>(Settings.RPCPort), e.what());
258 fflush(stderr);
259 GLog->Flush();
260 FPlatformMisc::RequestExit(true);
261 return;
262 }
263
264 OnPreTickHandle = FWorldDelegates::OnWorldTickStart.AddRaw(
265 this,
267 OnPostTickHandle = FWorldDelegates::OnWorldPostActorTick.AddRaw(
268 this,
271 this,
273 OnPreWorldInitializationHandle = FWorldDelegates::OnPreWorldInitialization.AddRaw(
274 this,
276
277 bIsRunning = true;
278
279 // check to convert this as secondary server
280 if (!PrimaryIP.empty())
281 {
282 // we are secondary server, connecting to primary server
283 bIsPrimaryServer = false;
285
286 // Clients subscribe with the primary's stream ids only (see GET_TOKEN).
288 // Except for this server's own world snapshots, which direct clients of
289 // this server read; the primary never routes its stream 1 here.
290 {
291 const auto BroadcastStreamId =
293 Server.GetStreamingServer().SetStreamAlias(BroadcastStreamId, BroadcastStreamId);
294 }
295
296 // ROS commands address the sensor by the primary's stream id.
297 auto FindAliasedStream = [this](const carla::Buffer &Data)
298 -> std::optional<carla::streaming::detail::stream_id_type>
299 {
300 carla::streaming::detail::stream_id_type PrimaryStreamId = 0u;
301 if (Data.size() < sizeof(PrimaryStreamId))
302 {
303 return std::nullopt;
304 }
305 std::memcpy(&PrimaryStreamId, Data.data(), sizeof(PrimaryStreamId));
306 return Server.GetStreamingServer().FindStreamAlias(PrimaryStreamId);
307 };
308
309 // define the commands executor (when a command comes from the primary server)
310 auto CommandExecutor = [=, this](carla::multigpu::MultiGPUCommand Id, carla::Buffer Data) {
311 switch (Id) {
313 {
314 TRACE_CPUPROFILER_EVENT_SCOPE_STR("MultiGPUCommand::SEND_FRAME");
315 // Parsed outside the lock, so a large frame does not hold up the
316 // game thread. Only a complete frame is queued: a frame is never
317 // played in part.
318 FFrameData Frame;
319 const carla::multigpu::FrameDataReadResult Result = Frame.Read(Data.data(), Data.size());
320 const uint64_t FrameNumber = Frame.GetFrameNumber();
321 bool bReportDrop = false;
322 bool bReportFormat = false;
323 {
324 std::scoped_lock<std::mutex> Lock(FrameToProcessMutex);
325 if (CurrentEpisode && !Result.IsComplete())
326 {
327 bReportDrop = (DroppedFrames++ == 0u);
328 // This server missed the primary's state: get all of it again.
329 bResyncRequested = true;
330 }
331 else if (CurrentEpisode)
332 {
333 bReportFormat = ((Result.skipped > 0u) || (Result.grown > 0u)) && !bWarnedFrameFormat;
334 bWarnedFrameFormat = bWarnedFrameFormat || bReportFormat;
335 {
336 TRACE_CPUPROFILER_EVENT_SCOPE_STR("FramesToProcess.emplace_back");
337 FramesToProcess.emplace_back(std::move(Frame));
339 }
340 // SEND_FRAME has no acknowledgement or backpressure, so a growing
341 // backlog means this secondary is falling behind; surface it.
342 // Logged only on state change, with separate warn/recovery
343 // thresholds (hysteresis) so a backlog oscillating near one
344 // value can't flip the log every tick.
345 static constexpr int32 BacklogWarningThreshold = 5;
346 static constexpr int32 BacklogRecoveryThreshold = 2;
347 const int32 BacklogSize = static_cast<int32>(FramesToProcess.size());
348 if (BacklogSize > BacklogWarningThreshold && !bFramesToProcessBacklogged)
349 {
350 UE_LOG(LogCarla, Warning,
351 TEXT("Secondary server is falling behind the primary: %d frames queued"),
352 BacklogSize);
354 }
355 else if (BacklogSize <= BacklogRecoveryThreshold && bFramesToProcessBacklogged)
356 {
357 UE_LOG(LogCarla, Log, TEXT("Secondary server has caught up with the primary"));
359 }
360 }
361 }
362 // carla::log_* rather than UE_LOG, so that Shipping logs show them too.
363 if (bReportDrop)
364 {
365 carla::log_error("multigpu: dropping a frame from the primary:",
366 carla::multigpu::FrameDataStatusName(Result.status), "at packet",
367 static_cast<uint32_t>(Result.packet_id), "(offset", Result.offset, "declaring",
368 Result.packet_size, "bytes) of a", Data.size(),
369 "byte frame. Every server must run the same CARLA build.");
370 }
371 if (bReportFormat)
372 {
373 carla::log_warning("multigpu: frame", FrameNumber, "from the primary carries",
374 Result.skipped, "packets this build does not know (first id",
375 static_cast<uint32_t>(Result.first_skipped_id), ") and", Result.grown,
376 "packets longer than it reads. Every server must run the same CARLA build.");
377 }
378 break;
379 }
381 {
383 std::string_view(reinterpret_cast<const char *>(Data.data()), Data.size()));
384 {
385 std::scoped_lock<std::mutex> Lock(FrameToProcessMutex);
386 PendingLoadMap = UTF8_TO_TCHAR(Request.map.c_str());
388 PendingLoadMapId = Request.load_id;
390 bLoadMapPending = true;
391 }
392 break;
393 }
395 {
396 // Resolved by actor id: local stream ids follow this secondary's own creation order.
398 std::optional<carla::streaming::detail::token_type> Token;
399 if (Data.size() >= sizeof(Request))
400 {
401 std::memcpy(&Request, Data.data(), sizeof(Request));
402 Token = SensorStreams.Resolve(Server.GetStreamingServer(), Request.actor_id, Request.stream_id);
403 }
404 if (!Token)
405 {
406 carla::log_warning("multigpu: cannot provide a token for sensor actor ", Request.actor_id,
407 " (", Data.size(), "-byte request); replying not ready");
408 carla::Buffer Reply(
409 reinterpret_cast<const unsigned char *>(carla::multigpu::kTokenNotReadyMarker.data()),
411 Secondary->Write(std::move(Reply));
412 break;
413 }
414 carla::Buffer Reply(reinterpret_cast<const unsigned char *>(&*Token), sizeof(*Token));
415 carla::log_info("responding with a token for port ", Token->get_port());
416 Secondary->Write(std::move(Reply));
417 break;
418 }
420 {
421 std::string msg("Yes, I'm alive");
422 carla::Buffer buf((unsigned char *) msg.c_str(), (size_t) msg.size());
423 carla::log_info("responding is alive command");
424 Secondary->Write(std::move(buf));
425 break;
426 }
428 {
429 const auto sensor_id = FindAliasedStream(Data);
430 if (sensor_id)
431 {
433 }
434 const bool res = sensor_id.has_value();
435 carla::Buffer buf(reinterpret_cast<const unsigned char *>(&res), sizeof(res));
436 carla::log_info("responding ENABLE_ROS with: ", res);
437 Secondary->Write(std::move(buf));
438 break;
439 }
441 {
442 const auto sensor_id = FindAliasedStream(Data);
443 if (sensor_id)
444 {
446 }
447 const bool res = sensor_id.has_value();
448 carla::Buffer buf(reinterpret_cast<const unsigned char *>(&res), sizeof(res));
449 carla::log_info("responding DISABLE_ROS with: ", res);
450 Secondary->Write(std::move(buf));
451 break;
452 }
454 {
455 const auto sensor_id = FindAliasedStream(Data);
456 const bool res = sensor_id && Server.GetStreamingServer().IsEnabledForROS(*sensor_id);
457 carla::Buffer buf(reinterpret_cast<const unsigned char *>(&res), sizeof(res));
458 carla::log_info("responding IS_ENABLED_ROS with: ", res);
459 Secondary->Write(std::move(buf));
460 break;
461 }
462 }
463 };
464
465 Secondary = std::make_shared<carla::multigpu::Secondary>(PrimaryIP, PrimaryPort, CommandExecutor);
466 Secondary->Connect();
467 // set this server in synchronous mode
468 bSynchronousMode = true;
469 // From here on, OnBeginFrame sets this server's time step from every
470 // primary frame; the benchmarking switch must not override it.
471 FApp::SetBenchmarking(false);
472 OnBeginFrameHandle = FCoreDelegates::OnBeginFrame.AddRaw(this, &FCarlaEngine::OnBeginFrame);
473 if (GEngine)
474 {
475 OnTravelFailureHandle = GEngine->OnTravelFailure().AddLambda(
476 [this](UWorld *, ETravelFailure::Type, const FString &Error)
477 {
478 bTravelFailed = true;
479 carla::log_error("multigpu: failed to load the map the primary loaded:", TCHAR_TO_UTF8(*Error));
480 });
481 }
482 if (!FApp::CanEverRender())
483 {
484 UE_LOG(LogCarla, Warning,
485 TEXT("Multi-GPU secondary started without rendering (-nullrhi): camera sensors routed here will produce no data"));
486 }
487 }
488 else
489 {
490 // we are primary server, starting server
491 bIsPrimaryServer = true;
493 SecondaryServer->SetNewConnectionCallback([this]()
494 {
495 this->bNewConnection = true;
496 UE_LOG(LogCarla, Log, TEXT("New secondary connection detected"));
497 });
498 if (!FApp::CanEverRender())
499 {
500 UE_LOG(LogCarla, Log,
501 TEXT("Rendering disabled (-nullrhi): running as a non-rendering, authority-only primary. Camera sensors need a connected rendering secondary server to produce data."));
502 }
503 }
504 }
505
506 // create ROS2 manager
507 #if defined(WITH_ROS2)
508 if (Settings.ROS2)
509 {
510 UE_LOG(LogCarla, Log, TEXT("ROS2: Creating ROS2 Instance..."));
511 auto ROS2 = carla::ros2::ROS2::GetInstance();
512 const std::string Rmw = TCHAR_TO_UTF8(*Settings.RmwName);
513 const auto Parsed = carla::ros2::MiddlewareFromString(Rmw);
514 if (!Parsed.valid)
515 {
516 UE_LOG(LogCarla, Error,
517 TEXT("ROS2: unrecognized --rmw value '%s'. Available: %s. ROS2 is DISABLED for this session."),
518 *Settings.RmwName,
519 UTF8_TO_TCHAR(carla::ros2::GetAvailableMiddleware().c_str()));
520 }
521 else
522 {
523 int32 DomainId = Settings.ROS2DomainId;
524 if (DomainId != carla::ros2::kUnsetDomainId && !carla::ros2::IsValidDomainId(DomainId))
525 {
526 UE_LOG(LogCarla, Error,
527 TEXT("ROS2: --ros-domain-id=%d is out of range [%d, %d]; using the default domain."),
530 }
531 if (!ROS2->Enable(true, Parsed.middleware, DomainId))
532 {
533 UE_LOG(LogCarla, Error,
534 TEXT("ROS2: --rmw='%s' is not compiled into this binary. Available: %s. ROS2 is DISABLED for this session."),
535 *Settings.RmwName,
536 UTF8_TO_TCHAR(carla::ros2::GetAvailableMiddleware().c_str()));
537 }
538 else
539 {
540 UE_LOG(LogCarla, Log, TEXT("ROS2: enabled with middleware '%s'."), *Settings.RmwName);
541 if (Settings.RmwName == TEXT("fastdds"))
542 {
543 // Mirrors the transport default applied in FastDDSSharedParticipant:
544 // UDPv4-only unless FASTDDS_BUILTIN_TRANSPORTS is set (Fast-DDS
545 // shared memory silently drops data across container/uid/version
546 // boundaries, so it is opt-in).
547 const char *TransportsEnv = std::getenv("FASTDDS_BUILTIN_TRANSPORTS");
548 if (TransportsEnv != nullptr)
549 {
550 UE_LOG(LogCarla, Log,
551 TEXT("ROS2: Fast-DDS builtin transports set from FASTDDS_BUILTIN_TRANSPORTS='%s'."),
552 UTF8_TO_TCHAR(TransportsEnv));
553 }
554 else
555 {
556 UE_LOG(LogCarla, Log,
557 TEXT("ROS2: Fast-DDS transport: UDPv4 only (default; set FASTDDS_BUILTIN_TRANSPORTS=DEFAULT to re-enable shared memory)."));
558 }
559 }
560 // Apply the configured default topic visibility before any sensor stream is
561 // created. Gated on Settings.ROS2 so non-ROS2 runs never force every stream
562 // active (which would make every sensor produce data each tick).
563 Server.GetStreamingServer().SetROS2TopicVisibilityDefaultEnabled(Settings.ROS2TopicVisibility);
564 }
565 }
566 } else {
567 UE_LOG(LogCarla, Log, TEXT("ROS2: ROS2 enabled..."));
568 }
569 #else
570 UE_LOG(LogCarla, Log, TEXT("ROS2: ROS2 extension not build..."));
571 #endif
572
573 bMapChanged = true;
574}
575
577{
578 TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__);
580 {
581 std::scoped_lock<std::mutex> Lock(FrameToProcessMutex);
582 CurrentEpisode = &Episode;
583 // The new episode has a new actor registry.
584 MappedId.clear();
586 DroppedFrames = 0u;
587 bWarnedFrameFormat = false;
588 }
589
590 // Reset map settings
591 UWorld* World = CurrentEpisode->GetWorld();
592 ALargeMapManager* LargeMapManager = UCarlaStatics::GetLargeMapManager(World);
593 if (LargeMapManager)
594 {
597 }
598
599 // The settings survive load_world(reset_settings=False) but the traffic
600 // light manager is new with every map, and a map can enable sign snapping
601 // on its own. Enable it on whichever side lacks it so that they agree.
603 if (ATrafficLightManager* TrafficLightManager =
604 GameMode ? GameMode->GetTrafficLightManager() : nullptr)
605 {
607 {
608 TrafficLightManager->SetAdjustSignsHeightToGround(true);
609 }
610 else
611 {
613 TrafficLightManager->GetAdjustSignsHeightToGround();
614 }
615 }
616
617 if (!bIsPrimaryServer)
618 {
619 // set this secondary server with no-rendering mode
621 // and synchronous, as it plays the primary's frames: its own clients (read-only
622 // queries) then never wait for a tick that only the primary triggers
624 }
625
627
628 ResetFrameCounter(GFrameNumber);
629
630 // make connection between Episode and Recorder
631 if (Recorder)
632 {
633 Recorder->SetEpisode(&Episode);
634 Episode.SetRecorder(Recorder);
636 }
637
638 Server.NotifyBeginEpisode(Episode);
639
641
643 {
644 PreparedFrame.reset();
645 LastPlayedFrame.reset();
646 bWarnedUntimedFrame = false;
647 bWarnedUnpairedFrame = false;
648 bWarnedNoSensorTicks = false;
649 RestoredPoses = 0u;
650 RestoredPoseClasses.Reset();
651 // Map actors that simulate physics (movable props) would fall or drift on
652 // their own here; the primary's frames move them instead.
653 for (auto &It : Episode.GetActorRegistry())
654 {
655 FCarlaActor *CarlaActor = It.Value.Get();
656 if ((CarlaActor != nullptr) && (CarlaActor->GetActorType() == FCarlaActor::ActorType::Other) &&
657 !CarlaActor->IsDormant() && (CarlaActor->GetActor() != nullptr))
658 {
659 if (auto *Root = Cast<UPrimitiveComponent>(CarlaActor->GetActor()->GetRootComponent()))
660 {
661 if (Root->IsSimulatingPhysics())
662 {
663 Root->SetSimulatePhysics(false);
664 }
665 }
666 }
667 }
668 if (bTravelFailed)
669 {
670 // The engine fell back to another map: playing the primary's frames here
671 // would mirror them onto the wrong world.
672 bTravelFailed = false;
673 DetachFromPrimary("could not load the primary's map; all servers must run the same build with the same content");
674 return;
675 }
676 if (!LoadingMap.IsEmpty() &&
677 !FPaths::GetBaseFilename(LoadingMap).Equals(Episode.GetMapName(), ESearchCase::IgnoreCase))
678 {
679 UE_LOG(LogCarla, Warning, TEXT("Multi-GPU: the primary loaded %s and this secondary is on %s"),
680 *LoadingMap, *Episode.GetMapName());
681 }
682
685
686 // Re-arms the primary's full-resync flag (see Router::HandleResponse),
687 // since connect-time arming predates this secondary's own level load.
689 EpisodeReadyPendingSince = FPlatformTime::Seconds();
691 }
692}
693
695{
696 if (!bEpisodeReadyPending || CurrentEpisode == nullptr)
697 {
698 return;
699 }
700 // The primary mirrors frames only after EPISODE_READY, so its first frame finds the furniture spawned.
701 for (TActorIterator<AOpenDriveGenerator> It(CurrentEpisode->GetWorld()); It; ++It)
702 {
703 if (It->IsStreetFurnitureGenerating())
704 {
705 return;
706 }
707 }
708 bEpisodeReadyPending = false;
709 UE_LOG(LogCarla, Log, TEXT("Multi-GPU: episode content ready after %.2f s, sending EPISODE_READY"),
710 FPlatformTime::Seconds() - EpisodeReadyPendingSince);
712}
713
715{
717 Secondary->Write(carla::Buffer(reinterpret_cast<const unsigned char *>(Ready.data()), Ready.size()));
718}
719
720void FCarlaEngine::DetachFromPrimary(const char *Reason)
721{
722 carla::log_error("multigpu:", Reason, "- this secondary stops mirroring the primary until it is restarted");
723 if (Secondary)
724 {
725 // Joins the command threads; nothing they run waits on this thread.
726 Secondary->Stop();
727 }
728 {
729 std::scoped_lock<std::mutex> Lock(FrameToProcessMutex);
730 FramesToProcess.clear();
731 QueuedFrames = 0u;
732 }
733 PreparedFrame.reset();
734 LastPlayedFrame.reset();
735 FApp::SetUseFixedTimeStep(false);
736}
737
739{
742 uint64_t Dropped = 0u;
743 {
744 std::scoped_lock<std::mutex> Lock(FrameToProcessMutex);
745 CurrentEpisode = nullptr;
746 FramesToProcess.clear();
747 QueuedFrames = 0u;
749 Dropped = DroppedFrames;
750 DroppedFrames = 0u;
751 }
752 if (Dropped > 1u)
753 {
754 carla::log_error("multigpu: dropped", Dropped, "incomplete frames from the primary in this episode");
755 }
756 if (DeltaMismatches > 0u)
757 {
758 carla::log_warning("multigpu:", DeltaMismatches, "frames of this episode ran with another time step than the primary's");
759 DeltaMismatches = 0u;
760 }
761 if (RestoredPoses > 0u)
762 {
763 carla::log_warning("multigpu:", RestoredPoses, "mirror poses had moved before their capture in this episode and were restored");
764 RestoredPoses = 0u;
765 }
766 PreparedFrame.reset();
767 LastPlayedFrame.reset();
768}
769
770void FCarlaEngine::BindReplayedSensor(uint32_t PrimaryActorId, uint32_t LocalActorId, bool bCreated)
771{
772 FCarlaActor *CarlaActor = CurrentEpisode->FindCarlaActor(LocalActorId);
773 if ((CarlaActor == nullptr) || (CarlaActor->GetActorType() != FCarlaActor::ActorType::Sensor))
774 {
775 return;
776 }
777
779 {
781 {
782 UE_LOG(LogCarla, Warning,
783 TEXT("Multi-GPU: ignoring replayed sensor %u (primary actor %u): the episode of the latest map load is not open here (did the level load fail?); its clients get no data until it is"),
784 LocalActorId, PrimaryActorId);
786 }
787 return;
788 }
789
790 ASensor *Sensor = Cast<ASensor>(CarlaActor->GetActor());
791 const bool bSensorHasStream = (Sensor != nullptr) && Sensor->IsStreamReady();
792 std::optional<carla::streaming::detail::stream_id_type> OwnStreamId;
793 if (bSensorHasStream)
794 {
796 }
797 else if (FActorSensorData *DormantData = CarlaActor->GetActorData<FActorSensorData>())
798 {
799 if (DormantData->Stream.IsStreamReady())
800 {
801 OwnStreamId = carla::streaming::detail::token_type(DormantData->Stream.GetToken()).get_stream_id();
802 }
803 }
804 if (!OwnStreamId)
805 {
806 UE_LOG(LogCarla, Warning,
807 TEXT("Multi-GPU: replayed sensor %u (primary actor %u) has no stream; its clients get no data"),
808 LocalActorId, PrimaryActorId);
809 return;
810 }
811
812 // Only a sensor created by this replay step has never ticked; a live
813 // sensor's stream must not be replaced (its capture reads it concurrently).
814 const bool bCanAdopt = bCreated && bSensorHasStream;
815 auto ReservedStream = SensorStreams.Bind(Server.GetStreamingServer(), PrimaryActorId, *OwnStreamId, bCanAdopt);
816 if (!ReservedStream)
817 {
818 return;
819 }
820 Sensor->SetDataStream(FDataStream(std::move(*ReservedStream)));
821 Server.GetStreamingServer().CloseStream(*OwnStreamId);
822}
823
825{
826 if (!SecondaryServer || !SecondaryServer->IsAnySecondaryLoading())
827 {
828 return;
829 }
830 TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__);
831 static constexpr double TimeoutSeconds = 30.0;
832 const double Start = FPlatformTime::Seconds();
833 const double Deadline = Start + TimeoutSeconds;
834 UE_LOG(LogCarla, Log, TEXT("Multi-GPU: waiting for secondary servers to load the new episode"));
835 while (SecondaryServer->IsAnySecondaryLoading())
836 {
838 {
839 // This frame belongs to a world being left: the new one waits instead.
840 return;
841 }
842 if (FPlatformTime::Seconds() >= Deadline)
843 {
844 UE_LOG(LogCarla, Warning,
845 TEXT("Multi-GPU: secondary servers did not load the new episode within %.0f s (waited %.0f ms); ticking without waiting"),
846 TimeoutSeconds, (FPlatformTime::Seconds() - Start) * 1000.0);
847 SecondaryServer->StopWaitingForSecondaryLoads();
848 return;
849 }
850 Server.RunSome(1u);
851 }
852 UE_LOG(LogCarla, Log, TEXT("Multi-GPU: all secondary servers loaded the new episode after %.0f ms"),
853 (FPlatformTime::Seconds() - Start) * 1000.0);
854}
855
856void FCarlaEngine::OnPreTick(UWorld *World, ELevelTick TickType, float DeltaSeconds)
857{
858 TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__);
859 if (TickType != ELevelTick::LEVELTICK_All)
860 {
861 return;
862 }
863
864 if (!bIsPrimaryServer)
865 {
868 CurrentEpisode != nullptr,
869 PreparedFrame.has_value(),
870 (CurrentEpisode != nullptr) && (World == CurrentEpisode->GetWorld()));
872 {
873 FString MapToLoad;
875 {
876 std::scoped_lock<std::mutex> Lock(FrameToProcessMutex);
877 MapToLoad = std::move(PendingLoadMap);
878 PendingLoadMap.Reset();
879 Extras = std::move(PendingLoadMapExtras);
883 bLoadMapPending = false;
884 }
885 PreparedFrame.reset();
886 LoadingMap = MapToLoad;
887 // A map this package lacks would make the engine fall back to another
888 // one, and frames of the primary's world would then be played there.
889 if (UCarlaStatics::FindMapPath(FPaths::GetBaseFilename(MapToLoad)).IsEmpty())
890 {
891 DetachFromPrimary(TCHAR_TO_UTF8(*FString::Printf(
892 TEXT("the primary loaded %s, which this server does not have"), *MapToLoad)));
893 return;
894 }
896 UGameplayStatics::OpenLevel(CurrentEpisode->GetWorld(), *MapToLoad, true);
897 // Frames still queued belong to the world being left; NotifyEndEpisode drops them.
898 return;
899 }
900
902 {
903 // An engine frame on this server's own clock: no primary frame, so no
904 // simulation time passes either.
905 return;
906 }
907
908 std::optional<FFrameData> Frame;
909 {
910 std::scoped_lock<std::mutex> Lock(FrameToProcessMutex);
911 if (!FramesToProcess.empty() && (FramesToProcess.front().GetFrameNumber() == *PreparedFrame))
912 {
913 Frame.emplace(std::move(FramesToProcess.front()));
914 FramesToProcess.erase(FramesToProcess.begin());
916 }
917 }
918 if (Frame)
919 {
920 // Played outside the lock, so the command thread is never held up by
921 // a large resync.
922 PlayPrimaryFrame(*World, DeltaSeconds, *Frame);
923 }
924 else if (!bWarnedUnpairedFrame)
925 {
927 UE_LOG(LogCarla, Warning, TEXT("Multi-GPU: primary frame %llu, prepared for this engine frame, is gone"),
928 static_cast<uint64>(*PreparedFrame));
929 }
930 PreparedFrame.reset();
931 return;
932 }
933
934 PendingFrameTime.reset();
935 if (CurrentEpisode && !bSynchronousMode && SecondaryServer->HasClientsConnected())
936 {
937 // set synchronous mode
943 }
944
945 // process RPC commands
946 do
947 {
948 Server.RunSome(1u);
949 }
951
952 // load_world() only schedules the travel, for the next engine frame: this
953 // frame still belongs to the world being left, and is never sent.
954 bool bLeavingWorld = FCarlaEngine_IsWorldTravelPending(World);
955
956 // Frames sent while a secondary is still loading are dropped there.
957 if (bSynchronousMode && !bLeavingWorld)
958 {
960 // Another client's load_world can be served while waiting.
961 bLeavingWorld = FCarlaEngine_IsWorldTravelPending(World);
962 }
963
964 if (CurrentEpisode && (World == CurrentEpisode->GetWorld()) && !bLeavingWorld)
965 {
966 // The world's clocks before UWorld::Tick adds this frame's delta to them.
967 const AWorldSettings *WorldSettings = World->GetWorldSettings();
969 Time.DeltaSeconds = DeltaSeconds;
970 Time.TimeSeconds = World->TimeSeconds;
971 Time.UnpausedTimeSeconds = World->UnpausedTimeSeconds;
972 Time.RealTimeSeconds = World->RealTimeSeconds;
973 Time.AudioTimeSeconds = World->AudioTimeSeconds;
974 Time.TimeDilation = WorldSettings ? WorldSettings->GetEffectiveTimeDilation() : 1.0f;
975 PendingFrameTime = Time;
976 }
977
978 // update frame counter
980
981 if (CurrentEpisode)
982 {
983 CurrentEpisode->TickTimers(DeltaSeconds);
984 }
985}
986
988{
989 TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__);
990 bFramePlayedThisTick = false;
991 PreparedFrame.reset();
992 if (!Secondary)
993 {
994 return;
995 }
996
997 const bool bConnected = Secondary->IsConnected();
998 if (bWasConnectedToPrimary && !bConnected)
999 {
1000 carla::log_warning("multigpu: lost the connection to the primary; this secondary stops mirroring "
1001 "it and runs on its own clock until it is restarted");
1002 }
1003 bWasConnectedToPrimary = bConnected;
1004
1006 {
1007 const double Now = FPlatformTime::Seconds();
1008 if (Now - LastResyncRequestSeconds >= 1.0)
1009 {
1011 bResyncRequested = false;
1012 UE_LOG(LogCarla, Log, TEXT("Multi-GPU: missed state of the primary; asking it for a resync"));
1014 }
1015 }
1016
1018
1020 State.connected = bConnected;
1021 State.has_episode = (CurrentEpisode != nullptr);
1022 State.travel_pending = State.has_episode && FCarlaEngine_IsWorldTravelPending(CurrentEpisode->GetWorld());
1023 // While EPISODE_READY is held back, frames run on this server's own clock so the content can finish.
1024 State.load_map_pending = bLoadMapPending || bEpisodeReadyPending;
1025 State.exit_requested = IsEngineExitRequested();
1027
1029 {
1030 // Not mirroring anything: serve RPCs on this server's own clock, without
1031 // spinning (RunSome returns at once when there is no work).
1032 Server.RunSome(1u);
1033 static constexpr double MinFrameSeconds = 1.0 / 50.0;
1034 const double Elapsed = FPlatformTime::Seconds() - LastOwnClockFrameSeconds;
1035 if (Elapsed < MinFrameSeconds)
1036 {
1037 FPlatformProcess::Sleep(static_cast<float>(MinFrameSeconds - Elapsed));
1038 }
1039 LastOwnClockFrameSeconds = FPlatformTime::Seconds();
1040 FApp::SetUseFixedTimeStep(false);
1041 return;
1042 }
1043
1044 // Never wait without an episode or while a map load is pending or a travel
1045 // is scheduled: the primary waits for this server's EPISODE_READY, which is
1046 // only sent from NotifyBeginEpisode, inside the travel.
1047 const bool bCanWait = (Action == carla::multigpu::BeginFrameAction::Wait);
1048 std::optional<uint64_t> Frame;
1049 std::optional<float> Delta;
1050 do
1051 {
1052 Server.RunSome(1u);
1053 if (!bCanWait || bLoadMapPending || IsEngineExitRequested() || !Secondary->IsConnected())
1054 {
1055 break;
1056 }
1057 if (QueuedFrames.load() == 0u)
1058 {
1059 // RunSome returns at once when it has nothing to do: without this the
1060 // loop spins a core until the command thread queues a frame. A sleep on
1061 // the monotonic clock, unlike a timed condition wait with this libc++.
1062 FPlatformProcess::SleepNoStats(0.0005f);
1063 continue;
1064 }
1065 std::scoped_lock<std::mutex> Lock(FrameToProcessMutex);
1066 if (!FramesToProcess.empty())
1067 {
1068 Frame = FramesToProcess.front().GetFrameNumber();
1069 if (const carla::multigpu::FrameTime *Time = FramesToProcess.front().GetFrameTime())
1070 {
1071 Delta = Time->delta_seconds;
1072 }
1073 }
1074 }
1075 while (!Frame);
1076
1077 // This engine frame's time step: UEngine::UpdateTimeAndHandleMaxTickRate,
1078 // which runs right after this delegate, turns it back into this very float,
1079 // so the world ticks with exactly the primary's delta.
1080 const bool bTimed = Delta && carla::multigpu::IsUsableDelta(*Delta);
1081 FApp::SetUseFixedTimeStep(bTimed);
1082 if (bTimed)
1083 {
1084 FApp::SetFixedDeltaTime(static_cast<double>(*Delta));
1085 }
1086 PreparedFrame = Frame;
1087}
1088
1089void FCarlaEngine::PlayPrimaryFrame(UWorld &World, float DeltaSeconds, FFrameData &Frame)
1090{
1091 TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__);
1092 const uint64_t FrameNumber = Frame.GetFrameNumber();
1093 const carla::multigpu::FrameTime *Time = Frame.GetFrameTime();
1094 if (Time)
1095 {
1096 if (DeltaSeconds != Time->delta_seconds)
1097 {
1098 if (DeltaMismatches++ == 0u)
1099 {
1100 carla::log_warning("multigpu: frame", FrameNumber, "ran with a", DeltaSeconds,
1101 "s time step instead of the primary's", Time->delta_seconds, "s");
1102 }
1103 }
1104
1105 // The primary's world clocks before this frame's accumulation: UWorld::Tick
1106 // then adds the same float to the same doubles, so the world time materials,
1107 // animations and tick intervals see is the primary's, bit for bit.
1108 World.TimeSeconds = Time->world_time_seconds;
1109 World.UnpausedTimeSeconds = Time->world_unpaused_time_seconds;
1110 World.RealTimeSeconds = Time->world_real_time_seconds;
1111 World.AudioTimeSeconds = Time->world_audio_time_seconds;
1112 if (AWorldSettings *WorldSettings = World.GetWorldSettings())
1113 {
1114 if (WorldSettings->TimeDilation != Time->time_dilation)
1115 {
1116 WorldSettings->TimeDilation = Time->time_dilation;
1117 }
1118 }
1119
1120 const bool bConsecutive = carla::multigpu::IsConsecutive(LastPlayedFrame, FrameNumber);
1122 {
1123 // A frame went missing: this server lacks whatever it carried.
1124 bResyncRequested = true;
1125 }
1126 if (!bConsecutive)
1127 {
1128 // What the primary's clocks read while it served the RPCs this frame
1129 // replays (spawns, attachments): before its TickTimers of this frame.
1131 Time->elapsed_game_time - Time->delta_seconds,
1132 Time->visual_game_time - Time->delta_seconds);
1133 FCarlaEngine::ResetFrameCounter(FrameNumber - 1u);
1134 UE_LOG(LogCarla, Log, TEXT("Multi-GPU: following the primary's clock from frame %llu (elapsed %.6f s)"),
1135 static_cast<uint64>(FrameNumber), Time->elapsed_game_time);
1136 }
1137 MirrorPrimarySettings(*Time);
1138 if (!Frame.HasSensorTicks() && !bWarnedNoSensorTicks)
1139 {
1140 bWarnedNoSensorTicks = true;
1141 carla::log_warning("multigpu: the primary does not say which sensors tick; no sensor of this "
1142 "secondary will produce data. Every server must run the same CARLA build.");
1143 }
1144 }
1145
1146 {
1147 TRACE_CPUPROFILER_EVENT_SCOPE_STR("FramesToProcess.PlayFrameData");
1148 // Ends with the frame counter set to the primary's frame number.
1149 Frame.PlayFrameData(
1151 MappedId,
1153 [this](uint32_t PrimaryActorId, uint32_t LocalActorId, bool bCreated)
1154 {
1155 BindReplayedSensor(PrimaryActorId, LocalActorId, bCreated);
1156 },
1157 [this](uint32_t PrimaryActorId)
1158 {
1159 SensorStreams.Unbind(Server.GetStreamingServer(), PrimaryActorId);
1160 });
1161 }
1162
1163 if (Time)
1164 {
1166 LastPlayedFrame = FrameNumber;
1167 }
1168 else
1169 {
1171 {
1172 bWarnedUntimedFrame = true;
1173 carla::log_warning("multigpu: the primary sends frames without its clock (an older build?); "
1174 "this secondary stamps them with its own. Every server must run the same CARLA build.");
1175 }
1176 CurrentEpisode->TickTimers(DeltaSeconds);
1177 LastPlayedFrame.reset();
1178 }
1179 bFramePlayedThisTick = true;
1180}
1181
1183{
1184 TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__);
1185 for (const auto &Played : MirrorState.PlayedPoses)
1186 {
1187 FCarlaActor *CarlaActor = CurrentEpisode->FindCarlaActor(Played.first);
1188 if ((CarlaActor == nullptr) || CarlaActor->IsDormant() || (CarlaActor->GetActor() == nullptr))
1189 {
1190 continue;
1191 }
1192 const FTransform Current = CarlaActor->GetActorGlobalTransform();
1193 const FTransform &Pose = Played.second;
1194 // 1e-3 cm and about 1e-4 degrees
1195 const bool bMoved =
1196 !Current.GetLocation().Equals(Pose.GetLocation(), 1e-3) ||
1197 (Current.GetRotation().AngularDistance(Pose.GetRotation()) > FMath::DegreesToRadians(1e-4));
1198 if (!bMoved)
1199 {
1200 continue;
1201 }
1202 AActor *Actor = CarlaActor->GetActor();
1203 FTransform Restored = Pose;
1204 Restored.SetScale3D(Actor->GetActorScale3D());
1205 CarlaActor->SetActorGlobalTransform(Restored, ETeleportType::None);
1206 ++RestoredPoses;
1207 const FName ClassName = Actor->GetClass()->GetFName();
1208 if (!RestoredPoseClasses.Contains(ClassName))
1209 {
1210 RestoredPoseClasses.Add(ClassName);
1211 UE_LOG(LogCarla, Warning,
1212 TEXT("Multi-GPU: a %s mirror moved on its own between playing frame %llu and its capture; restored the primary's pose"),
1213 *ClassName.ToString(), static_cast<uint64>(FCarlaEngine::GetFrameCounter()));
1214 }
1215 }
1216}
1217
1219{
1221 SettingsFromFrameTime(Time, Settings);
1222 // A secondary plays the primary's frames, so it is synchronous whatever the
1223 // primary runs, and never renders its own viewport.
1224 Settings.bSynchronousMode = true;
1225 Settings.bNoRenderingMode = true;
1227 {
1228 return;
1229 }
1230 CurrentEpisode->ApplySettings(Settings);
1231 // set_episode_settings applies these two itself; do the same here.
1233 {
1234 if (ALargeMapManager *LargeMap = GameMode->GetLMManager())
1235 {
1236 LargeMap->ConsiderSpectatorAsEgo(Settings.SpectatorAsEgo);
1237 }
1238 if (ATrafficLightManager *TrafficLightManager = GameMode->GetTrafficLightManager())
1239 {
1240 TrafficLightManager->SetAdjustSignsHeightToGround(Settings.bAdjustSignsHeightToGround);
1241 }
1242 }
1243 UE_LOG(LogCarla, Log, TEXT("Multi-GPU: mirroring the primary's settings (fixed delta %s, substepping %s)"),
1244 Settings.FixedDeltaSeconds.IsSet() ? *FString::SanitizeFloat(Settings.FixedDeltaSeconds.GetValue()) : TEXT("none"),
1245 Settings.bSubstepping ? TEXT("on") : TEXT("off"));
1246}
1247
1248
1249void FCarlaEngine::OnPostTick(UWorld *World, ELevelTick TickType, float DeltaSeconds)
1250{
1251 TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__);
1252 // With -RenderOffScreen nobody can see the main viewport, but its world
1253 // render still costs a full scene pass (Lumen, shadows, clouds) every frame
1254 // next to the sensors' own captures. Keep it disabled in that mode; sensors
1255 // are independent scene captures and are unaffected.
1256 static const bool bRenderOffScreen =
1257 FParse::Param(FCommandLine::Get(), TEXT("RenderOffScreen"));
1258 if (bRenderOffScreen && GEngine && GEngine->GameViewport &&
1259 !GEngine->GameViewport->bDisableWorldRendering)
1260 {
1261 GEngine->GameViewport->bDisableWorldRendering = true;
1262 }
1263 // FScene::SceneFrameNumber normally advances once per main-viewport world
1264 // render (FRendererModule::BeginRenderingViewFamilies). With the viewport's
1265 // world render disabled it freezes, every scene capture gets
1266 // ViewFamily.FrameNumber == 0, and all frame-keyed renderer caches (global
1267 // distance field, virtual shadow maps, Lumen temporal state) treat every
1268 // capture as "never rendered" and rebuild from scratch. Advance it manually
1269 // so sensors keep the normal per-frame cache behavior.
1270 if (World && World->Scene && GEngine && GEngine->GameViewport &&
1271 GEngine->GameViewport->bDisableWorldRendering)
1272 {
1273 World->Scene->IncrementFrameNumber();
1274 }
1275 // tick the recorder/replayer system
1276 if (GetCurrentEpisode())
1277 {
1278 // First, so that the frame sent below carries what a replay just set
1279 // (actor poses, visual time), as the primary's own snapshot does.
1280 auto* EpisodeRecorder = GetCurrentEpisode()->GetRecorder();
1281 if (EpisodeRecorder)
1282 {
1283 EpisodeRecorder->Ticking(DeltaSeconds);
1284 }
1285
1286 // Only frames whose start OnPreTick recorded, of the episode's world, and
1287 // never the frame that leaves it.
1288 if (bIsPrimaryServer && PendingFrameTime && SecondaryServer->HasClientsConnected() &&
1289 (World == GetCurrentEpisode()->GetWorld()) && !FCarlaEngine_IsWorldTravelPending(World))
1290 {
1291 const bool bWasNewConnection = bNewConnection.exchange(false);
1292 FFrameData &FrameData = GetCurrentEpisode()->GetFrameData();
1293 FrameData.GetFrameData(GetCurrentEpisode(), true, bWasNewConnection);
1294
1297 Time.delta_seconds = PendingFrameTime->DeltaSeconds;
1300 Time.world_time_seconds = PendingFrameTime->TimeSeconds;
1301 Time.world_unpaused_time_seconds = PendingFrameTime->UnpausedTimeSeconds;
1302 Time.world_real_time_seconds = PendingFrameTime->RealTimeSeconds;
1303 Time.world_audio_time_seconds = PendingFrameTime->AudioTimeSeconds;
1304 Time.time_dilation = PendingFrameTime->TimeDilation;
1305 Time.full_resync = bWasNewConnection;
1306 FrameTimeFromSettings(GetCurrentEpisode()->GetSettings(), Time);
1307 FrameData.SetFrameTime(Time);
1308
1309 std::ostringstream OutStream;
1310 FrameData.Write(OutStream);
1311
1312 // send frame data to secondary
1313 std::string Tmp(OutStream.str());
1314 SecondaryServer->GetCommander().SendFrameData(carla::Buffer(std::move((unsigned char *) Tmp.c_str()), (size_t) Tmp.size()));
1315
1316#if !UE_BUILD_SHIPPING
1317 if (CVarCarlaMultiGpuVerifyFrames.GetValueOnGameThread() != 0)
1318 {
1319 FFrameData Check;
1321 Check.Read(reinterpret_cast<const uint8_t *>(Tmp.data()), Tmp.size());
1322 if (!Result.IsComplete() || (Result.skipped > 0u) || (Result.grown > 0u))
1323 {
1324 UE_LOG(LogCarla, Error,
1325 TEXT("Multi-GPU: the frame sent to the secondaries does not read back: %s at packet %u (offset %llu), %u unknown and %u longer packets"),
1326 UTF8_TO_TCHAR(carla::multigpu::FrameDataStatusName(Result.status)),
1327 static_cast<uint32>(Result.packet_id), static_cast<uint64>(Result.offset),
1328 Result.skipped, Result.grown);
1329 }
1330 }
1331#endif // !UE_BUILD_SHIPPING
1332
1333 // Game thread, like the get_sensor_token RPC: it must not run inside a
1334 // Router callback, and the secondary answers GET_TOKEN on its command
1335 // thread, so this cannot wait on either game thread.
1336 if (bWasNewConnection)
1337 {
1338 const std::size_t Rerouted = SecondaryServer->GetCommander().RerouteLostSensors();
1339 if (Rerouted > 0u)
1340 {
1341 UE_LOG(LogCarla, Log, TEXT("Multi-GPU: routed %d sensors of a lost secondary to a new one"),
1342 static_cast<int32>(Rerouted));
1343 }
1344 }
1345 }
1346
1347 // Every server records its own events here, with or without secondaries
1348 // (a secondary never sends them), so they never pile up.
1350 }
1351 PendingFrameTime.reset();
1352
1353 // A secondary only captures and broadcasts frames that carry the primary's
1354 // state, on the world it was played on.
1355 const bool bCaptureThisFrame = bIsPrimaryServer ||
1356 (bFramePlayedThisTick && CurrentEpisode && (World == CurrentEpisode->GetWorld()));
1357 if ((TickType == ELevelTick::LEVELTICK_All) && (CurrentEpisode != nullptr) && bCaptureThisFrame)
1358 {
1359 if (!bIsPrimaryServer)
1360 {
1362 }
1363
1364 // Look for lightsubsystem
1365 bool LightUpdatePending = false;
1366 if (World)
1367 {
1368 UCarlaLightSubsystem* CarlaLightSubsystem = World->GetSubsystem<UCarlaLightSubsystem>();
1369 if (CarlaLightSubsystem)
1370 {
1371 LightUpdatePending = CarlaLightSubsystem->IsUpdatePending();
1372 }
1373 }
1374
1375 // send the worldsnapshot
1376 WorldObserver.BroadcastTick(*CurrentEpisode, DeltaSeconds, bMapChanged, LightUpdatePending);
1377 CurrentEpisode->GetSensorManager().PostPhysTick(World, TickType, DeltaSeconds);
1379 }
1380 bFramePlayedThisTick = false;
1381}
1382
1383void FCarlaEngine::OnPreWorldInitialization(UWorld *World, const UWorld::InitializationValues IVS)
1384{
1385 // Chaos rebuilds its dynamic BVH tree in an async task and swaps it in only
1386 // when that task has finished, so the frame a contact is first detected
1387 // depends on wall-clock timing. Its grid has no such history. Chaos reads
1388 // this cvar when it builds a world's acceleration structures, and changing
1389 // it while they exist breaks the swap (a check in
1390 // CopyUnBuiltDynamicAccelerationStructures), hence here and not on
1391 // apply_settings.
1392 static const TCHAR *CVarName = TEXT("p.Chaos.AccelerationStructureUseDynamicTree");
1393 IConsoleVariable *CVar = IConsoleManager::Get().FindConsoleVariable(CVarName);
1394 if (CVar == nullptr)
1395 {
1397 {
1398 UE_LOG(LogCarla, Warning, TEXT("%s not found: collisions are not deterministic"), CVarName);
1399 }
1400 return;
1401 }
1403 {
1405 {
1407 }
1408 CVar->Set(0, ECVF_SetByCode);
1409 }
1411 {
1412 CVar->Set(DynamicTreeBeforeDeterministicCollisions.GetValue(), ECVF_SetByCode);
1414 }
1415}
1416
1418{
1420
1421 // A secondary plays the primary's frames: it is synchronous whatever it is told.
1423
1424#if WITH_EDITOR
1425 if (GEngine && GEngine->GameViewport)
1426 {
1427 // -RenderOffScreen keeps the invisible main viewport's world render off
1428 // regardless of the episode's no-rendering setting (see OnPostTick).
1429 GEngine->GameViewport->bDisableWorldRendering = Settings.bNoRenderingMode ||
1430 FParse::Param(FCommandLine::Get(), TEXT("RenderOffScreen"));
1431 }
1432#endif
1433 if (bIsPrimaryServer)
1434 {
1436 }
1437 else
1438 {
1439 // OnBeginFrame sets this server's time step from every primary frame. Its
1440 // sensor streams wait for slow clients instead of dropping their data.
1442 }
1443
1444 // Setting parameters for physics substepping
1445 UPhysicsSettings* PhysSett = UPhysicsSettings::Get();
1446 PhysSett->bSubstepping = Settings.bSubstepping;
1447 PhysSett->MaxSubstepDeltaTime = Settings.MaxSubstepDeltaTime;
1448 PhysSett->MaxSubsteps = Settings.MaxSubsteps;
1449
1450 UWorld* World = CurrentEpisode->GetWorld();
1451 ALargeMapManager* LargeMapManager = UCarlaStatics::GetLargeMapManager(World);
1452 if (LargeMapManager)
1453 {
1454 LargeMapManager->SetLayerStreamingDistance(Settings.TileStreamingDistance);
1455 LargeMapManager->SetActorStreamingDistance(Settings.ActorActiveDistance);
1456 }
1457}
1458
1460{
1461 bMapChanged = false;
1462}
static void FCarlaEngine_SetFixedDeltaSeconds(TOptional< double > FixedDeltaSeconds)
static bool FCarlaEngine_IsWorldTravelPending(const UWorld *World)
Whether a travel to another map is scheduled for World: load_world() only schedules it,...
static void FCarlaEngine_PrepareGeneratedWorld(UWorld *World, const carla::multigpu::LoadMapExtras &Extras)
Multi-GPU secondary: the world the primary generated from OpenDRIVE, where this server's OpenDriveMap...
static TAutoConsoleVariable< int32 > CVarCarlaMultiGpuVerifyFrames(TEXT("carla.MultiGpu.VerifyFrames"), 1, TEXT("does not parse completely with this build (1 = on, 0 = off)."), ECVF_Default)
static uint32 FCarlaEngine_GetNumberOfThreadsForRPCServer()
static bool FCarlaEngine_SameSettings(const FEpisodeSettings &A, const FEpisodeSettings &B)
static TOptional< double > FCarlaEngine_GetFixedDeltaSeconds()
FDataStreamTmpl< carla::streaming::Stream > FDataStream
Definition DataStream.h:80
void FrameTimeFromSettings(const FEpisodeSettings &Settings, carla::multigpu::FrameTime &Time)
Copies the episode settings into a FrameTime and back.
void SettingsFromFrameTime(const carla::multigpu::FrameTime &Time, FEpisodeSettings &Settings)
Base class for the CARLA Game Mode.
ATrafficLightManager * GetTrafficLightManager()
void Ticking(float DeltaSeconds)
CarlaReplayer * GetReplayer(void)
void SetEpisode(UCarlaEpisode *ThisEpisode)
float GetLayerStreamingDistance() const
void SetActorStreamingDistance(float Distance)
void SetLayerStreamingDistance(float Distance)
float GetActorStreamingDistance() const
auto GetToken() const
Return the token that allows subscribing to this sensor's stream.
void SetDataStream(FDataStream InStream)
Replace the FDataStream associated with this sensor.
Class In charge of creating and assigning traffic light groups, controllers and components.
void CheckPlayAfterMapLoaded(void)
A view over an actor and its properties.
Definition CarlaActor.h:25
ActorType GetActorType() const
Definition CarlaActor.h:86
bool IsDormant() const
Definition CarlaActor.h:71
AActor * GetActor()
Definition CarlaActor.h:91
FTransform GetActorGlobalTransform() const
void SetActorGlobalTransform(const FTransform &Transform, ETeleportType Teleport=ETeleportType::TeleportPhysics)
FActorData * GetActorData()
Definition CarlaActor.h:205
void SendEpisodeReadyWhenContentReady()
Sends EPISODE_READY once the world's generated content has finished spawning.
void OnEpisodeSettingsChanged(const FEpisodeSettings &Settings)
uint64_t RestoredPoses
FDelegateHandle OnEpisodeSettingsChangeHandle
std::shared_ptr< carla::multigpu::Router > SecondaryServer
uint64_t DeltaMismatches
FString LoadingMap
std::atomic< size_t > QueuedFrames
std::mutex FrameToProcessMutex
void OnPreTick(UWorld *World, ELevelTick TickType, float DeltaSeconds)
TSet< FName > RestoredPoseClasses
bool bWarnedSensorBindRefused
static uint64_t FrameCounter
Definition CarlaEngine.h:43
carla::multigpu::SensorStreamRegistry::epoch_type PendingLoadEpoch
static uint64_t GetFrameCounter()
Definition CarlaEngine.h:73
void ResetSimulationState()
void MirrorPrimarySettings(const carla::multigpu::FrameTime &Time)
FWorldObserver WorldObserver
uint64_t DroppedFrames
FCarlaServer Server
FDelegateHandle OnBeginFrameHandle
static void ResetFrameCounter(uint64_t Value=0)
Definition CarlaEngine.h:89
bool bWarnedNoSensorTicks
void OnBeginFrame()
Secondary only (FCoreDelegates::OnBeginFrame, before the engine computes this frame's delta): waits f...
void NotifyEndEpisode()
std::atomic< bool > bResyncRequested
double LastOwnClockFrameSeconds
bool bFramesToProcessBacklogged
std::unordered_map< uint32_t, uint32_t > MappedId
double EpisodeReadyPendingSince
bool bWarnedFrameFormat
FEpisodeSettings CurrentSettings
carla::multigpu::SensorStreamRegistry::epoch_type LoadingEpoch
bool bEpisodeReadyPending
static uint64_t UpdateFrameCounter()
Definition CarlaEngine.h:78
void SendEpisodeReady()
Secondary only: tells the primary this episode is ready, which also makes it send a full resync.
std::atomic< bool > bLoadMapPending
UCarlaEpisode * CurrentEpisode
FMirrorState MirrorState
FDelegateHandle OnPreWorldInitializationHandle
std::vector< FFrameData > FramesToProcess
void DetachFromPrimary(const char *Reason)
Secondary only: stops mirroring the primary for good (it is gone, or this server cannot load its map)...
void BindReplayedSensor(uint32_t PrimaryActorId, uint32_t LocalActorId, bool bCreated)
Secondary only: binds a sensor replayed from the primary to the stream its clients were given (see ca...
carla::multigpu::LoadMapExtras PendingLoadMapExtras
std::atomic< bool > bNewConnection
std::optional< uint64_t > PreparedFrame
void NotifyBeginEpisode(UCarlaEpisode &Episode)
void NotifyInitGame(const UCarlaSettings &Settings)
TOptional< int32 > DynamicTreeBeforeDeterministicCollisions
Value of p.Chaos.AccelerationStructureUseDynamicTree before deterministic collisions changed it; unse...
carla::multigpu::load_map_id_type LoadingLoadMapId
std::optional< FPendingFrameTime > PendingFrameTime
ACarlaRecorder * Recorder
void PlayPrimaryFrame(UWorld &World, float DeltaSeconds, FFrameData &Frame)
Secondary only: plays the primary's frame on the frame OnBeginFrame prepared, with the primary's cloc...
bool bIsPrimaryServer
bool bWarnedUntimedFrame
bool bWarnedUnpairedFrame
bool bFramePlayedThisTick
double LastResyncRequestSeconds
FString PendingLoadMap
bool bSynchronousMode
void WaitForSecondaryEpisodes()
Primary only: keeps processing RPC until every secondary sent LOAD_MAP reports its episode ready (bou...
FDelegateHandle OnPreTickHandle
std::optional< uint64_t > LastPlayedFrame
void RestorePlayedPoses()
Secondary only: puts back any mirror that moved since its frame was played (a local mover the mirror ...
carla::multigpu::SensorStreamRegistry SensorStreams
bool bWasConnectedToPrimary
FDelegateHandle OnTravelFailureHandle
FDelegateHandle OnPostTickHandle
std::shared_ptr< carla::multigpu::Secondary > Secondary
UCarlaEpisode * GetCurrentEpisode()
Definition CarlaEngine.h:63
void OnPreWorldInitialization(UWorld *World, const UWorld::InitializationValues IVS)
Applies FEpisodeSettings::bDeterministicCollisions to the world about to be created.
carla::multigpu::load_map_id_type PendingLoadMapId
void OnPostTick(UWorld *World, ELevelTick TickType, float DeltaSeconds)
bool TickCueReceived()
std::shared_ptr< carla::multigpu::Router > GetSecondaryServer()
void RunSome(uint32 Milliseconds)
carla::streaming::Server & GetStreamingServer()
void NotifyEndEpisode()
void NotifyBeginEpisode(UCarlaEpisode &Episode)
void SetIsSecondaryServer(bool bIsSecondary)
A Multi-GPU secondary refuses every call that would change its world.
FDataMultiStream Start(uint16_t RPCPort, uint16_t StreamingPort, uint16_t SecondaryPort)
void AsyncRun(uint32 NumberOfWorkerThreads)
static FOnEpisodeSettingsChange OnEpisodeSettingsChange
void GetFrameData(UCarlaEpisode *ThisEpisode, bool bAdditionalData=false, bool bIncludeActorsAgain=false)
Definition FrameData.cpp:33
void Write(std::ostream &OutStream)
void SetFrameTime(const carla::multigpu::FrameTime &Time)
Definition FrameData.h:190
bool HasSensorTicks() const
Whether the frame says which sensors tick (only older builds do not).
Definition FrameData.h:188
uint64_t GetFrameNumber() const
Definition FrameData.h:185
carla::multigpu::FrameDataReadResult Read(const uint8_t *Data, size_t Size)
Parses one SEND_FRAME payload.
const carla::multigpu::FrameTime * GetFrameTime() const
The primary's clocks and settings of this frame; null for a frame from an older primary.
Definition FrameData.h:194
void Clear()
void PlayFrameData(UCarlaEpisode *ThisEpisode, std::unordered_map< uint32_t, uint32_t > &MappedId, FMirrorState &MirrorState, TFunctionRef< void(uint32_t, uint32_t, bool)> OnActorAdded, TFunctionRef< void(uint32_t)> OnActorRemoved)
OnActorAdded receives (primary actor id, local actor id, whether it was just created rather than reus...
void PostPhysTick(UWorld *World, ELevelTick TickType, float DeltaSeconds)
auto GetToken() const
Return the token that allows subscribing to this sensor's stream.
void SetStream(FDataMultiStream InStream)
Replace the Stream associated with this sensor.
void BroadcastTick(const UCarlaEpisode &Episode, float DeltaSeconds, bool MapChange, bool PendingLightUpdate)
Send a message to every connected client with the info about the given Episode.
Mounts CARLA content packs (content-only Unreal plugins cooked as DLC against a base release,...
static UCarlaContentPackManager * Get()
A simulation episode.
FFrameData & GetFrameData()
double GetVisualGameTime() const
Visual game seconds.
FEpisodeSettings EpisodeSettings
static void ClearGeneratedWorldFiles()
Delete Saved/OpenDrive|Nav/OpenDriveMap.
FCarlaActor * FindCarlaActor(FCarlaActor::IdType ActorId)
Find a Carla actor by id.
const FString & GetMapName() const
void ApplySettings(const FEpisodeSettings &Settings)
double GetElapsedGameTime() const
Game seconds since the start of this episode.
const FEpisodeSettings & GetSettings() const
void SetRecorder(ACarlaRecorder *Rec)
void SetTimers(double InElapsedGameTime, double InVisualGameTime)
Multi-GPU secondary: adopt the primary's clocks for the frame being played.
void TickTimers(float DeltaSeconds)
const FActorRegistry & GetActorRegistry() const
ACarlaRecorder * GetRecorder() const
FSensorManager & GetSensorManager()
The game instance contains elements that must be kept alive in between levels.
void SetOpendriveGenerationParameters(const carla::rpc::OpendriveGenerationParameters &Parameters)
Global settings for CARLA.
uint32 StreamingPort
setting for the streaming port.
std::string PrimaryIP
setting for the IP and Port of the primary server to connect.
uint32 RPCPort
World port to listen for client connections.
uint32 SecondaryPort
setting for the secondary servers port.
static FString FindMapPath(const FString &MapName)
static UCarlaGameInstance * GetGameInstance(const UObject *WorldContextObject)
static ALargeMapManager * GetLargeMapManager(const UObject *WorldContextObject)
static ACarlaGameModeBase * GetGameMode(const UObject *WorldContextObject)
static FString GetSavedXODRPath(const FString &MapName)
Where a generated OpenDRIVE world stores its xodr: <ProjectSavedDir>/OpenDrive/<MapName>....
static void SetSavedXODRDirectory(const FString &Directory)
Multi-GPU secondary: a folder of this process for the generated worlds the primary sends,...
A piece of raw data.
std::optional< token_type > Resolve(carla::streaming::Server &server, actor_id_type actor_id, stream_id_type primary_stream_id)
Token that serves primary actor actor_id under primary_stream_id, reserving a local stream if the sen...
epoch_type BeginEpisodeChange(carla::streaming::Server &server)
The primary switched episode: forgets every actor and returns the epoch the secondary must pass to Op...
bool IsEpisodeOpen()
Whether Bind()/Unbind() are accepted, i.e.
void OpenEpisode(epoch_type epoch)
Accepts binding if epoch is the latest one BeginEpisodeChange() issued.
std::optional< carla::streaming::Stream > Bind(carla::streaming::Server &server, actor_id_type actor_id, stream_id_type sensor_stream_id, bool can_adopt)
Returns the stream reserved for actor_id if can_adopt (the sensor has never ticked) and the sensor mu...
void Unbind(carla::streaming::Server &server, actor_id_type actor_id)
Forgets actor_id, closing its reserved stream if no sensor adopted it.
static std::shared_ptr< ROS2 > GetInstance()
Definition ROS2.h:61
std::optional< stream_id > FindStreamAlias(stream_id alias)
carla::streaming::detail::stream_id_type stream_id
bool IsEnabledForROS(stream_id sensor_id)
void CloseStream(carla::streaming::detail::stream_id_type id)
void EnableForROS(stream_id sensor_id)
void SetStreamAlias(stream_id alias, stream_id target)
void SetROS2TopicVisibilityDefaultEnabled(bool enabled)
void DisableForROS(stream_id sensor_id)
void SetSynchronousMode(bool is_synchro)
void SetSessionsUseStreamAliases(bool enabled)
Serializes a stream endpoint.
bool IsConsecutive(std::optional< uint64_t > last_played, uint64_t frame)
Whether frame directly follows the last frame this secondary played.
Definition frameGate.h:69
bool IsUsableDelta(float delta_seconds)
Whether a primary's delta can be this engine frame's time step.
Definition frameGate.h:80
BeginFrameAction DecideBeginFrame(const BeginFrameState &state)
Definition frameGate.h:34
const char * FrameDataStatusName(FrameDataStatus status)
LoadMapRequest ParseLoadMapPayload(std::string_view payload)
Definition commands.h:90
PreTickAction DecidePreTick(bool load_map_pending, bool has_episode, bool frame_prepared, bool episode_world)
Definition frameGate.h:54
BeginFrameAction
What a secondary does at the start of an engine frame, before the engine computes the frame's time st...
Definition frameGate.h:16
@ OwnClock
Not connected to a primary: run on this server's own clock.
@ Wait
Wait for the primary's next frame and take its time step.
bool NeedsResync(std::optional< uint64_t > last_played, uint64_t frame, bool full_resync)
Whether playing frame shows that this secondary missed primary state (a frame went missing) that only...
Definition frameGate.h:75
PreTickAction
What a secondary does when its world starts ticking.
Definition frameGate.h:45
@ Skip
No primary frame: let the frame pass without simulation time.
@ OpenLevel
Open the map the primary loaded; nothing else this frame.
constexpr std::string_view kTokenNotReadyMarker
GET_TOKEN reply when the secondary cannot resolve the sensor to a stream (or the request is malformed...
Definition commands.h:159
std::string MakeEpisodeReadyMessage(load_map_id_type load_id)
Episode-ready message: the marker followed by the load id of the LOAD_MAP that started the episode,...
Definition commands.h:124
std::string GetAvailableMiddleware()
bool IsValidDomainId(int id)
constexpr int kMinDomainId
Lowest valid ROS 2 domain id.
constexpr int kMaxDomainId
Highest valid ROS 2 domain id (RTPS maximum).
constexpr int kUnsetDomainId
Sentinel domain id meaning "no domain id was configured on the command line"; each middleware falls b...
MiddlewareParseResult MiddlewareFromString(const std::string &name)
Parse a middleware name string (lowercase).
Definition Middleware.h:42
static void log_error(Args &&... args)
Definition Logging.h:110
static void log_info(Args &&... args)
Definition Logging.h:82
static void log_warning(Args &&... args)
Definition Logging.h:96
std::vector< std::pair< uint32_t, FTransform > > PlayedPoses
The poses the last frame played set, by local id, to check before the capture that nothing moved a mi...
Definition FrameData.h:70
void Reset()
Definition FrameData.h:79
bool IsComplete() const
Only a complete frame may be played.
uint32_t skipped
Packets whose id this build does not know.
uint32_t grown
Known packets with trailing bytes this build does not read (written by a newer build).
uint8_t packet_id
Id, offset and declared size of the last packet looked at.
Frame data packet 25: the primary's clocks and episode settings for one frame.
Definition frameTime.h:20
double elapsed_game_time
UCarlaEpisode clocks at the end of frame N.
Definition frameTime.h:37
float delta_seconds
The raw DeltaSeconds that UWorld::Tick(N) received, before time dilation and clamping.
Definition frameTime.h:34
uint64_t frame
Frame counter of frame N; equal to the frame's FrameCounter packet.
Definition frameTime.h:30
bool full_resync
Set on the frame that rebuilds a secondary from scratch (a new or reconnected secondary,...
Definition frameTime.h:67
float time_dilation
AWorldSettings::GetEffectiveTimeDilation() of frame N.
Definition frameTime.h:63
double world_time_seconds
UWorld clocks at the start of frame N, before UWorld::Tick adds the delta to them.
Definition frameTime.h:42
GET_TOKEN request payload.
Definition commands.h:151
What a LOAD_MAP carries besides the map: for a world the primary generated from OpenDRIVE (generate_o...
Definition commands.h:51
std::optional< std::string > generation_parameters
Definition commands.h:53
std::optional< std::string > opendrive
Definition commands.h:52