From f2cf748009321d14ce4694dd308bf00e93b98303 Mon Sep 17 00:00:00 2001 From: nsubiron Date: Thu, 23 Mar 2017 12:27:40 +0100 Subject: [PATCH 1/3] Add support for depth cameras --- Source/Carla/Game/CarlaGameController.cpp | 69 ++++++++++++++--------- Source/Carla/Game/CarlaGameController.h | 4 +- Source/Carla/SceneCaptureCamera.cpp | 11 +++- Source/Carla/SceneCaptureCamera.h | 23 +++++++- 4 files changed, 76 insertions(+), 31 deletions(-) diff --git a/Source/Carla/Game/CarlaGameController.cpp b/Source/Carla/Game/CarlaGameController.cpp index edcbb35ee..0e19190fe 100644 --- a/Source/Carla/Game/CarlaGameController.cpp +++ b/Source/Carla/Game/CarlaGameController.cpp @@ -11,6 +11,8 @@ #include +#define CSTEXT(text) TEXT("CarlaServer: " text) + // ============================================================================= // -- Set functions ------------------------------------------------------------ // ============================================================================= @@ -52,7 +54,7 @@ static bool ReadSceneInit(carla::CarlaServer &Server) { carla::Mode mode; uint32 scene; - UE_LOG(LogCarla, Log, TEXT("Waiting for tryReadSceneInit...")); + UE_LOG(LogCarla, Log, CSTEXT("Waiting for tryReadSceneInit...")); while (!Server.tryReadSceneInit(mode, scene)) { if (Server.needsRestart()) return false; @@ -72,23 +74,23 @@ static bool SendAndReadSceneValues( for (APlayerStart *StartSpot : AvailableStartSpots) { check(StartSpot != nullptr); const FVector &Location = StartSpot->GetActorLocation(); - UE_LOG(LogCarla, Log, TEXT("Found start position {%f, %f}"), Location.X, Location.Y); + UE_LOG(LogCarla, Log, CSTEXT("Found start position {%f, %f}"), Location.X, Location.Y); sceneValues.possible_positions.push_back({Location.X, Location.Y}); } // Send scene values. - UE_LOG(LogCarla, Log, TEXT("Send scene values: %d positions"), sceneValues.possible_positions.size()); + UE_LOG(LogCarla, Log, CSTEXT("Send scene values: %d positions"), sceneValues.possible_positions.size()); Server.sendSceneValues(sceneValues); // Wait till we receive the answer. uint32 EndIndex; - UE_LOG(LogCarla, Log, TEXT("Waiting for episode start...")); + UE_LOG(LogCarla, Log, CSTEXT("Waiting for episode start...")); while (!Server.tryReadEpisodeStart(StartIndex, EndIndex)) { if (Server.needsRestart()) return false; } - UE_LOG(LogCarla, Log, TEXT("Episode start received: %d -> %d"), StartIndex, EndIndex); + UE_LOG(LogCarla, Log, CSTEXT("Episode start received: %d -> %d"), StartIndex, EndIndex); // Make sure the index is in range. if (StartIndex >= AvailableStartSpots.Num()) { - UE_LOG(LogCarla, Warning, TEXT("Received invalid start index, using zero instead")); + UE_LOG(LogCarla, Warning, CSTEXT("Received invalid start index, using zero instead")); StartIndex = 0u; } return true; @@ -97,7 +99,8 @@ static bool SendAndReadSceneValues( static void SendReward( carla::CarlaServer &Server, const ACarlaPlayerState &PlayerState, - const std::array &Cameras) + const std::array &RGBCameras, + const std::array &DepthCameras) { carla::Reward_Values reward; reward.timestamp = FMath::RoundHalfToZero(1000.0 * FPlatformTime::Seconds()); @@ -110,15 +113,15 @@ static void SendReward( Set(reward.collision_general, PlayerState.GetCollisionIntensityOther()); // Set(reward.intersect_other_lane, ); // Set(reward.intersect_offroad, ); - if (Cameras[0u] != nullptr) { - reward.image_width = Cameras[0u]->GetImageSizeX(); - reward.image_height = Cameras[0u]->GetImageSizeY(); + if (RGBCameras[0u] != nullptr) { // Do not add any camera if first is invalid. + reward.image_width = RGBCameras[0u]->GetImageSizeX(); + reward.image_height = RGBCameras[0u]->GetImageSizeY(); + Set(reward.image_rgb_0, RGBCameras[0u]); + Set(reward.image_rgb_1, RGBCameras[1u]); + Set(reward.image_depth_0, DepthCameras[0u]); + Set(reward.image_depth_1, DepthCameras[1u]); } - Set(reward.image_rgb_0, Cameras[0u]); - Set(reward.image_rgb_1, Cameras[1u]); - // Set(reward.image_depth_0, ); - // Set(reward.image_depth_1, ); - UE_LOG(LogCarla, Log, TEXT("Sending reward")); + UE_LOG(LogCarla, Log, CSTEXT("Sending reward")); Server.sendReward(reward); } @@ -129,7 +132,7 @@ static void TryReadControl(carla::CarlaServer &Server, ACarlaVehicleController & if (Server.tryReadControl(steer, throttle)) { Player.SetSteeringInput(steer); Player.SetThrottleInput(throttle); - UE_LOG(LogCarla, Log, TEXT("Read control: steer = %f, throttle = %f"), steer, throttle); + UE_LOG(LogCarla, Log, CSTEXT("Read control: steer = %f, throttle = %f"), steer, throttle); } } @@ -140,25 +143,27 @@ static void TryReadControl(carla::CarlaServer &Server, ACarlaVehicleController & CarlaGameController::CarlaGameController() : Server(MakeUnique(2001u, 2002u, 2000u)), Player(nullptr), - Cameras({{nullptr}}) {} + RGBCameras({{nullptr}}), + DepthCameras({{nullptr}}) {} CarlaGameController::~CarlaGameController() { - UE_LOG(LogCarla, Log, TEXT("Destroying CarlaGameController...")); + UE_LOG(LogCarla, Log, CSTEXT("Destroying CarlaGameController...")); } void CarlaGameController::Initialize() { if (bServerNeedsRestart) { - UE_LOG(LogCarla, Log, TEXT("Initializing CarlaServer")); + UE_LOG(LogCarla, Log, CSTEXT("Initializing CarlaServer")); Server->init(1u); if (ReadSceneInit(*Server)) { bServerNeedsRestart = false; } else { - UE_LOG(LogCarla, Warning, TEXT("Read scene init failed, server needs restart")); + UE_LOG(LogCarla, Warning, CSTEXT("Read scene init failed, server needs restart")); } } - Cameras = {{nullptr}}; + RGBCameras = {{nullptr}}; + DepthCameras = {{nullptr}}; } APlayerStart *CarlaGameController::ChoosePlayerStart( @@ -166,7 +171,7 @@ APlayerStart *CarlaGameController::ChoosePlayerStart( { uint32 StartIndex; if (!SendAndReadSceneValues(*Server, AvailableStartSpots, StartIndex)) { - UE_LOG(LogCarla, Warning, TEXT("Read scene values failed, server needs restart")); + UE_LOG(LogCarla, Warning, CSTEXT("Read scene values failed, server needs restart")); StartIndex = 0u; } return AvailableStartSpots[StartIndex]; @@ -180,17 +185,27 @@ void CarlaGameController::RegisterPlayer(AController &NewPlayer) void CarlaGameController::RegisterCaptureCamera(const ASceneCaptureCamera &CaptureCamera) { + const auto Effect = CaptureCamera.GetPostProcessEffect(); + check((Effect == EPostProcessEffect::None) || (Effect == EPostProcessEffect::Depth)) + auto &Cameras = (Effect == EPostProcessEffect::None ? RGBCameras : DepthCameras); + for (auto i = 0u; i < Cameras.size(); ++i) { if (Cameras[i] == nullptr) { Cameras[i] = &CaptureCamera; - UE_LOG(LogCarla, Log, TEXT("Registered capture camera %d"), i); + UE_LOG( + LogCarla, + Log, + CSTEXT("Registered capture camera %d with postprocess \"%s\""), + i, + *CaptureCamera.GetPostProcessEffectAsString()); return; } } UE_LOG( LogCarla, Warning, - TEXT("Attempting to register a camera but already have %d, captures from this camera won't be send"), + CSTEXT("Attempting to register a capture camera of type \"%d\" but already have %d, captures from this camera won't be sent"), + *CaptureCamera.GetPostProcessEffectAsString(), Cameras.size()); } @@ -208,18 +223,18 @@ void CarlaGameController::Tick(float DeltaSeconds) } if (Server->newEpisodeRequested()) { - UE_LOG(LogCarla, Log, TEXT("New episode requested")); + UE_LOG(LogCarla, Log, CSTEXT("New episode requested")); RestartLevel(false); return; } - SendReward(*Server, Player->GetPlayerState(), Cameras); + SendReward(*Server, Player->GetPlayerState(), RGBCameras, DepthCameras); TryReadControl(*Server, *Player); } void CarlaGameController::RestartLevel(bool ServerNeedsRestart) { - UE_LOG(LogCarla, Log, TEXT("Restarting...")); + UE_LOG(LogCarla, Log, CSTEXT("Restarting...")); bServerNeedsRestart = ServerNeedsRestart; Player->RestartLevel(); } diff --git a/Source/Carla/Game/CarlaGameController.h b/Source/Carla/Game/CarlaGameController.h index db4e01437..61a8a50f6 100644 --- a/Source/Carla/Game/CarlaGameController.h +++ b/Source/Carla/Game/CarlaGameController.h @@ -39,7 +39,9 @@ private: ACarlaVehicleController *Player; - std::array Cameras; + std::array RGBCameras; + + std::array DepthCameras; bool bServerNeedsRestart = true; }; diff --git a/Source/Carla/SceneCaptureCamera.cpp b/Source/Carla/SceneCaptureCamera.cpp index 966e40bfc..dd6f89f71 100644 --- a/Source/Carla/SceneCaptureCamera.cpp +++ b/Source/Carla/SceneCaptureCamera.cpp @@ -17,7 +17,8 @@ ASceneCaptureCamera::ASceneCaptureCamera(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer), SizeX(200u), - SizeY(200u) + SizeY(200u), + PostProcessEffect(EPostProcessEffect::None) { PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.TickGroup = TG_PrePhysics; @@ -92,6 +93,14 @@ void ASceneCaptureCamera::Tick(float Delta) RTResource->ReadPixels(ImageBitMap, ReadPixelFlags); } +FString ASceneCaptureCamera::GetPostProcessEffectAsString() const +{ + const UEnum* ptr = FindObject(ANY_PACKAGE, TEXT("EPostProcessEffect"), true); + if(!ptr) + return FString("Invalid"); + return ptr->GetEnumName(static_cast(PostProcessEffect)); +} + void ASceneCaptureCamera::UpdateDrawFrustum() { if(DrawFrustum && CaptureComponent2D) diff --git a/Source/Carla/SceneCaptureCamera.h b/Source/Carla/SceneCaptureCamera.h index cc89341ea..0932b58d3 100644 --- a/Source/Carla/SceneCaptureCamera.h +++ b/Source/Carla/SceneCaptureCamera.h @@ -15,7 +15,16 @@ class UDrawFrustumComponent; class USceneCaptureComponent2D; class UStaticMeshComponent; -UCLASS(hidecategories=(Collision, Material, Attachment, Actor)) +UENUM(BlueprintType) +enum class EPostProcessEffect : uint8 +{ + None UMETA(DisplayName = "RGB"), + Depth UMETA(DisplayName = "Depth Map"), + + SIZE UMETA(Hidden) +}; + +UCLASS(hidecategories=(Collision, Attachment, Actor)) class CARLA_API ASceneCaptureCamera : public AActor { GENERATED_BODY() @@ -44,6 +53,13 @@ public: return SizeY; } + EPostProcessEffect GetPostProcessEffect() const + { + return PostProcessEffect; + } + + FString GetPostProcessEffectAsString() const; + const TArray &GetImage() const { return ImageBitMap; @@ -61,6 +77,9 @@ private: UPROPERTY(Category = SceneCapture, EditAnywhere) uint32 SizeY; + UPROPERTY(Category = SceneCapture, EditAnywhere) + EPostProcessEffect PostProcessEffect; + /** To display the 3d camera in the editor. */ UPROPERTY() class UStaticMeshComponent* MeshComp; @@ -74,7 +93,7 @@ private: class UTextureRenderTarget2D* CaptureRenderTarget; /** Scene capture component. */ - UPROPERTY(Transient) + UPROPERTY(EditAnywhere) class USceneCaptureComponent2D* CaptureComponent2D; UPROPERTY() From 90dac9dc7f33ddaf40445e352089d7029d282c43 Mon Sep 17 00:00:00 2001 From: nsubiron Date: Thu, 23 Mar 2017 18:15:52 +0100 Subject: [PATCH 2/3] Reorganize where images are stored --- Source/Carla/Game/CarlaGameController.cpp | 72 ++++++------------ Source/Carla/Game/CarlaGameController.h | 6 -- Source/Carla/Game/CarlaGameControllerBase.h | 3 - Source/Carla/Game/CarlaGameMode.cpp | 9 ++- Source/Carla/Game/CarlaGameMode.h | 2 + Source/Carla/Game/CarlaPlayerState.cpp | 15 ++++ Source/Carla/Game/CarlaPlayerState.h | 80 ++++++++++++++++++-- Source/Carla/Game/CarlaVehicleController.cpp | 49 +++++++++++- Source/Carla/Game/CarlaVehicleController.h | 17 ++++- Source/Carla/Game/MockGameController.cpp | 5 -- Source/Carla/Game/MockGameController.h | 2 - Source/Carla/SceneCaptureCamera.cpp | 20 +++-- Source/Carla/SceneCaptureCamera.h | 10 +-- 13 files changed, 194 insertions(+), 96 deletions(-) diff --git a/Source/Carla/Game/CarlaGameController.cpp b/Source/Carla/Game/CarlaGameController.cpp index 0e19190fe..9e9067d68 100644 --- a/Source/Carla/Game/CarlaGameController.cpp +++ b/Source/Carla/Game/CarlaGameController.cpp @@ -32,11 +32,11 @@ static inline void Set(carla::Vector2D &cVector, const FVector &uVector) cVector = {uVector.X, uVector.Y}; } -static void Set(std::vector &cImage, const ASceneCaptureCamera *Camera) +static void Set(std::vector &cImage, const TArray &BitMap) { - if (Camera != nullptr) { - cImage.reserve(Camera->GetImage().Num()); - for (const auto &color : Camera->GetImage()) { + if (BitMap.Num() > 0) { + cImage.reserve(BitMap.Num()); + for (const auto &color : BitMap) { cImage.emplace_back(); cImage.back().R = color.R; cImage.back().G = color.G; @@ -98,12 +98,10 @@ static bool SendAndReadSceneValues( static void SendReward( carla::CarlaServer &Server, - const ACarlaPlayerState &PlayerState, - const std::array &RGBCameras, - const std::array &DepthCameras) + const ACarlaPlayerState &PlayerState) { carla::Reward_Values reward; - reward.timestamp = FMath::RoundHalfToZero(1000.0 * FPlatformTime::Seconds()); + reward.timestamp = PlayerState.GetTimeStamp(); Set(reward.player_location, PlayerState.GetLocation()); Set(reward.player_orientation, PlayerState.GetOrientation()); Set(reward.player_acceleration, PlayerState.GetAcceleration()); @@ -111,15 +109,21 @@ static void SendReward( Set(reward.collision_car, PlayerState.GetCollisionIntensityCars()); Set(reward.collision_pedestrian, PlayerState.GetCollisionIntensityPedestrians()); Set(reward.collision_general, PlayerState.GetCollisionIntensityOther()); - // Set(reward.intersect_other_lane, ); - // Set(reward.intersect_offroad, ); - if (RGBCameras[0u] != nullptr) { // Do not add any camera if first is invalid. - reward.image_width = RGBCameras[0u]->GetImageSizeX(); - reward.image_height = RGBCameras[0u]->GetImageSizeY(); - Set(reward.image_rgb_0, RGBCameras[0u]); - Set(reward.image_rgb_1, RGBCameras[1u]); - Set(reward.image_depth_0, DepthCameras[0u]); - Set(reward.image_depth_1, DepthCameras[1u]); + Set(reward.intersect_other_lane, PlayerState.GetOtherLaneIntersectionFactor()); + Set(reward.intersect_offroad, PlayerState.GetOffRoadIntersectionFactor()); + { // Add images. + using CPS = ACarlaPlayerState; + auto &ImageRGB0 = PlayerState.GetImage(CPS::ImageRGB0); + if (ImageRGB0.BitMap.Num() > 0) { + // Do not add any camera if first is invalid, also assume all the images + // have the same size. + reward.image_width = ImageRGB0.SizeX; + reward.image_height = ImageRGB0.SizeY; + Set(reward.image_rgb_0, ImageRGB0.BitMap); + Set(reward.image_rgb_1, PlayerState.GetImage(CPS::ImageRGB1).BitMap); + Set(reward.image_depth_0, PlayerState.GetImage(CPS::ImageDepth0).BitMap); + Set(reward.image_depth_1, PlayerState.GetImage(CPS::ImageDepth1).BitMap); + } } UE_LOG(LogCarla, Log, CSTEXT("Sending reward")); Server.sendReward(reward); @@ -142,9 +146,7 @@ static void TryReadControl(carla::CarlaServer &Server, ACarlaVehicleController & CarlaGameController::CarlaGameController() : Server(MakeUnique(2001u, 2002u, 2000u)), - Player(nullptr), - RGBCameras({{nullptr}}), - DepthCameras({{nullptr}}) {} + Player(nullptr) {} CarlaGameController::~CarlaGameController() { @@ -162,8 +164,6 @@ void CarlaGameController::Initialize() UE_LOG(LogCarla, Warning, CSTEXT("Read scene init failed, server needs restart")); } } - RGBCameras = {{nullptr}}; - DepthCameras = {{nullptr}}; } APlayerStart *CarlaGameController::ChoosePlayerStart( @@ -183,32 +183,6 @@ void CarlaGameController::RegisterPlayer(AController &NewPlayer) check(Player != nullptr); } -void CarlaGameController::RegisterCaptureCamera(const ASceneCaptureCamera &CaptureCamera) -{ - const auto Effect = CaptureCamera.GetPostProcessEffect(); - check((Effect == EPostProcessEffect::None) || (Effect == EPostProcessEffect::Depth)) - auto &Cameras = (Effect == EPostProcessEffect::None ? RGBCameras : DepthCameras); - - for (auto i = 0u; i < Cameras.size(); ++i) { - if (Cameras[i] == nullptr) { - Cameras[i] = &CaptureCamera; - UE_LOG( - LogCarla, - Log, - CSTEXT("Registered capture camera %d with postprocess \"%s\""), - i, - *CaptureCamera.GetPostProcessEffectAsString()); - return; - } - } - UE_LOG( - LogCarla, - Warning, - CSTEXT("Attempting to register a capture camera of type \"%d\" but already have %d, captures from this camera won't be sent"), - *CaptureCamera.GetPostProcessEffectAsString(), - Cameras.size()); -} - void CarlaGameController::BeginPlay() { Server->sendEndReset(); @@ -228,7 +202,7 @@ void CarlaGameController::Tick(float DeltaSeconds) return; } - SendReward(*Server, Player->GetPlayerState(), RGBCameras, DepthCameras); + SendReward(*Server, Player->GetPlayerState()); TryReadControl(*Server, *Player); } diff --git a/Source/Carla/Game/CarlaGameController.h b/Source/Carla/Game/CarlaGameController.h index 61a8a50f6..9760f1703 100644 --- a/Source/Carla/Game/CarlaGameController.h +++ b/Source/Carla/Game/CarlaGameController.h @@ -25,8 +25,6 @@ public: virtual void RegisterPlayer(AController &NewPlayer) override; - virtual void RegisterCaptureCamera(const ASceneCaptureCamera &CaptureCamera) override; - virtual void BeginPlay() override; virtual void Tick(float DeltaSeconds) override; @@ -39,9 +37,5 @@ private: ACarlaVehicleController *Player; - std::array RGBCameras; - - std::array DepthCameras; - bool bServerNeedsRestart = true; }; diff --git a/Source/Carla/Game/CarlaGameControllerBase.h b/Source/Carla/Game/CarlaGameControllerBase.h index 5aa758b4b..b8bd4da57 100644 --- a/Source/Carla/Game/CarlaGameControllerBase.h +++ b/Source/Carla/Game/CarlaGameControllerBase.h @@ -6,7 +6,6 @@ class AController; class APlayerStart; -class ASceneCaptureCamera; /// Base class for a CARLA game controller. class CARLA_API CarlaGameControllerBase @@ -21,8 +20,6 @@ public: virtual void RegisterPlayer(AController &NewPlayer) = 0; - virtual void RegisterCaptureCamera(const ASceneCaptureCamera &CaptureCamera) = 0; - virtual void BeginPlay() = 0; virtual void Tick(float DeltaSeconds) = 0; diff --git a/Source/Carla/Game/CarlaGameMode.cpp b/Source/Carla/Game/CarlaGameMode.cpp index e0f9b2274..fe757013a 100644 --- a/Source/Carla/Game/CarlaGameMode.cpp +++ b/Source/Carla/Game/CarlaGameMode.cpp @@ -16,7 +16,8 @@ ACarlaGameMode::ACarlaGameMode() : Super(), - GameController(nullptr) + GameController(nullptr), + PlayerController(nullptr) { PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.TickGroup = TG_PrePhysics; @@ -79,7 +80,10 @@ void ACarlaGameMode::RegisterCaptureCamera(ASceneCaptureCamera &CaptureCamera) { check(GameController != nullptr); AddTickPrerequisiteActor(&CaptureCamera); - GameController->RegisterCaptureCamera(CaptureCamera); + ACarlaVehicleController *Player = Cast(PlayerController); + if (Player != nullptr) { + Player->RegisterCaptureCamera(CaptureCamera); + } } void ACarlaGameMode::RegisterPlayer(AController &NewPlayer) @@ -87,6 +91,7 @@ void ACarlaGameMode::RegisterPlayer(AController &NewPlayer) check(GameController != nullptr); AddTickPrerequisiteActor(&NewPlayer); GameController->RegisterPlayer(NewPlayer); + PlayerController = &NewPlayer; } APlayerStart *ACarlaGameMode::FindUnOccupiedStartPoints( diff --git a/Source/Carla/Game/CarlaGameMode.h b/Source/Carla/Game/CarlaGameMode.h index efbaed10c..214e80982 100644 --- a/Source/Carla/Game/CarlaGameMode.h +++ b/Source/Carla/Game/CarlaGameMode.h @@ -48,4 +48,6 @@ private: TArray &UnOccupiedStartPoints); CarlaGameControllerBase *GameController; + + AController *PlayerController; }; diff --git a/Source/Carla/Game/CarlaPlayerState.cpp b/Source/Carla/Game/CarlaPlayerState.cpp index 6568271ff..a1756f4c1 100644 --- a/Source/Carla/Game/CarlaPlayerState.cpp +++ b/Source/Carla/Game/CarlaPlayerState.cpp @@ -6,9 +6,14 @@ void ACarlaPlayerState::Reset() { Super::Reset(); + // Reset incremental values. CollisionIntensityCars = 0.0f; CollisionIntensityPedestrians = 0.0f; CollisionIntensityOther = 0.0f; + // Invalidate images. + for (auto &image : Images) { + image = Image(); // Reset. + } } void ACarlaPlayerState::CopyProperties(APlayerState *PlayerState) @@ -19,6 +24,7 @@ void ACarlaPlayerState::CopyProperties(APlayerState *PlayerState) ACarlaPlayerState *Other = Cast(PlayerState); if (Other != nullptr) { + TimeStamp = Other->TimeStamp; Location = Other->Location; Orientation = Other->Orientation; Acceleration = Other->Acceleration; @@ -26,6 +32,10 @@ void ACarlaPlayerState::CopyProperties(APlayerState *PlayerState) CollisionIntensityCars = Other->CollisionIntensityCars; CollisionIntensityPedestrians = Other->CollisionIntensityPedestrians; CollisionIntensityOther = Other->CollisionIntensityOther; + OtherLaneIntersectionFactor = Other->OtherLaneIntersectionFactor; + OffRoadIntersectionFactor = Other->OffRoadIntersectionFactor; + Images = Other->Images; + UE_LOG(LogCarla, Log, TEXT("Copied properties of ACarlaPlayerState")); } } } @@ -34,3 +44,8 @@ void ACarlaPlayerState::RegisterCollision(AActor */*Actor*/, FVector NormalImpul { CollisionIntensityOther += NormalImpulse.Size(); } + +void ACarlaPlayerState::UpdateTimeStamp() +{ + TimeStamp = FMath::RoundHalfToZero(1000.0 * FPlatformTime::Seconds()); +} diff --git a/Source/Carla/Game/CarlaPlayerState.h b/Source/Carla/Game/CarlaPlayerState.h index a3eff62d7..1c97f8ae0 100644 --- a/Source/Carla/Game/CarlaPlayerState.h +++ b/Source/Carla/Game/CarlaPlayerState.h @@ -2,24 +2,56 @@ #pragma once +#include #include "GameFramework/PlayerState.h" #include "CarlaPlayerState.generated.h" -/** - * - */ +/// Current state of the player, updated every frame by ACarlaVehicleController. +/// +/// This class matches the reward that it is sent to the client over the +/// network. UCLASS() class CARLA_API ACarlaPlayerState : public APlayerState { GENERATED_BODY() + // =========================================================================== + // -- Types ------------------------------------------------------------------ + // =========================================================================== +public: + + enum ImageTag { + ImageRGB0, + ImageRGB1, + ImageDepth0, + ImageDepth1, + NUMBER_OF_IMAGES + }; + + struct Image { + uint32 SizeX = 0u; + uint32 SizeY = 0u; + TArray BitMap; + }; + + // =========================================================================== + // -- APlayerState ----------------------------------------------------------- + // =========================================================================== public: virtual void Reset() override; virtual void CopyProperties(APlayerState *PlayerState) override; - void RegisterCollision(AActor *Actor, FVector NormalImpulse); + // =========================================================================== + // -- Getters ---------------------------------------------------------------- + // =========================================================================== +public: + + int32 GetTimeStamp() const + { + return TimeStamp; + } const FVector &GetLocation() const { @@ -56,21 +88,59 @@ public: return CollisionIntensityOther; } + float GetOtherLaneIntersectionFactor() const + { + return OtherLaneIntersectionFactor; + } + + float GetOffRoadIntersectionFactor() const + { + return OffRoadIntersectionFactor; + } + + const Image &GetImage(ImageTag Tag) const + { + return Images[Tag]; + } + + // =========================================================================== + // -- Modifiers -------------------------------------------------------------- + // =========================================================================== +private: + + void RegisterCollision(AActor *Actor, FVector NormalImpulse); + + void UpdateTimeStamp(); + + // =========================================================================== + // -- Private members -------------------------------------------------------- + // =========================================================================== private: friend class ACarlaVehicleController; + // If you add another variable here, don't forget to copy it inside + // CopyProperties if necessary. + + int32 TimeStamp; + FVector Location; FVector Orientation; FVector Acceleration; - float ForwardSpeed; + float ForwardSpeed = 0.0f; float CollisionIntensityCars = 0.0f; float CollisionIntensityPedestrians = 0.0f; float CollisionIntensityOther = 0.0f; + + float OtherLaneIntersectionFactor = 0.0f; + + float OffRoadIntersectionFactor = 0.0f; + + std::array Images; }; diff --git a/Source/Carla/Game/CarlaVehicleController.cpp b/Source/Carla/Game/CarlaVehicleController.cpp index b50d879bb..90f93cceb 100644 --- a/Source/Carla/Game/CarlaVehicleController.cpp +++ b/Source/Carla/Game/CarlaVehicleController.cpp @@ -86,17 +86,36 @@ void ACarlaVehicleController::Possess(APawn *aPawn) } } +static void ReadCameraPixels(const ASceneCaptureCamera *Camera, ACarlaPlayerState::Image &Image) +{ + if (Camera != nullptr) { + if (Camera->ReadPixels(Image.BitMap)) { + Image.SizeX = Camera->GetImageSizeX(); + Image.SizeY = Camera->GetImageSizeY(); + } else { + Image.BitMap.Empty(); // Clears the array. + } + } +} + void ACarlaVehicleController::Tick(float DeltaTime) { Super::PlayerTick(DeltaTime); if (IsPossessingAVehicle()) { + CarlaPlayerState->UpdateTimeStamp(); CarlaPlayerState->Location = GetVehicleLocation(); const FVector PreviousSpeed = CarlaPlayerState->ForwardSpeed * CarlaPlayerState->Orientation; CarlaPlayerState->Orientation = GetVehicleOrientation(); CarlaPlayerState->ForwardSpeed = GetVehicleForwardSpeed(); const FVector CurrentSpeed = CarlaPlayerState->ForwardSpeed * CarlaPlayerState->Orientation; CarlaPlayerState->Acceleration = (CurrentSpeed - PreviousSpeed) / DeltaTime; + /// @todo Set intersection factors. + using CPS = ACarlaPlayerState; + ReadCameraPixels(RGBCameras[0u], CarlaPlayerState->Images[CPS::ImageRGB0]); + ReadCameraPixels(RGBCameras[1u], CarlaPlayerState->Images[CPS::ImageRGB1]); + ReadCameraPixels(DepthCameras[0u], CarlaPlayerState->Images[CPS::ImageDepth0]); + ReadCameraPixels(DepthCameras[1u], CarlaPlayerState->Images[CPS::ImageDepth1]); } } @@ -127,10 +146,34 @@ FVector ACarlaVehicleController::GetVehicleOrientation() const return GetPawn()->GetTransform().GetRotation().GetForwardVector(); } -void ACarlaVehicleController::ResetPlayerState() +// ============================================================================= +// -- Scene capture ------------------------------------------------------------ +// ============================================================================= + +void ACarlaVehicleController::RegisterCaptureCamera(const ASceneCaptureCamera &CaptureCamera) { - check(CarlaPlayerState != nullptr); - CarlaPlayerState->Reset(); + const auto Effect = CaptureCamera.GetPostProcessEffect(); + check((Effect == EPostProcessEffect::None) || (Effect == EPostProcessEffect::Depth)) + auto &Cameras = (Effect == EPostProcessEffect::None ? RGBCameras : DepthCameras); + + for (auto i = 0u; i < Cameras.size(); ++i) { + if (Cameras[i] == nullptr) { + Cameras[i] = &CaptureCamera; + UE_LOG( + LogCarla, + Log, + TEXT("Registered capture camera %d with postprocess \"%s\""), + i, + *CaptureCamera.GetPostProcessEffectAsString()); + return; + } + } + UE_LOG( + LogCarla, + Warning, + TEXT("Attempting to register a capture camera of type \"%d\" but already have %d, captures from this camera won't be sent"), + *CaptureCamera.GetPostProcessEffectAsString(), + Cameras.size()); } // ============================================================================= diff --git a/Source/Carla/Game/CarlaVehicleController.h b/Source/Carla/Game/CarlaVehicleController.h index 0f9864f13..66728a960 100644 --- a/Source/Carla/Game/CarlaVehicleController.h +++ b/Source/Carla/Game/CarlaVehicleController.h @@ -6,6 +6,7 @@ #include "CarlaVehicleController.generated.h" class ACarlaPlayerState; +class ASceneCaptureCamera; class UCameraComponent; class USpringArmComponent; class UWheeledVehicleMovementComponent; @@ -48,6 +49,7 @@ public: /// @name Vehicle pawn info // =========================================================================== /// @{ +public: bool IsPossessingAVehicle() const { @@ -68,7 +70,14 @@ public: return *CarlaPlayerState; } - void ResetPlayerState(); + /// @} + // =========================================================================== + /// @name Scene Capture + // =========================================================================== + /// @{ +public: + + void RegisterCaptureCamera(const ASceneCaptureCamera &CaptureCamera); /// @} // =========================================================================== @@ -166,4 +175,10 @@ private: // Cast for quick access to the custom player state. UPROPERTY() ACarlaPlayerState *CarlaPlayerState; + + using CaptureCameraArray = std::array; + + CaptureCameraArray RGBCameras; + + CaptureCameraArray DepthCameras; }; diff --git a/Source/Carla/Game/MockGameController.cpp b/Source/Carla/Game/MockGameController.cpp index 6f9a48237..5ddc84d63 100644 --- a/Source/Carla/Game/MockGameController.cpp +++ b/Source/Carla/Game/MockGameController.cpp @@ -24,11 +24,6 @@ void MockGameController::RegisterPlayer(AController &NewPlayer) } } -void MockGameController::RegisterCaptureCamera(const ASceneCaptureCamera &CaptureCamera) -{ - -} - void MockGameController::BeginPlay() { diff --git a/Source/Carla/Game/MockGameController.h b/Source/Carla/Game/MockGameController.h index 4ad938773..f41b952b4 100644 --- a/Source/Carla/Game/MockGameController.h +++ b/Source/Carla/Game/MockGameController.h @@ -15,8 +15,6 @@ public: virtual void RegisterPlayer(AController &NewPlayer) override; - virtual void RegisterCaptureCamera(const ASceneCaptureCamera &CaptureCamera) override; - virtual void BeginPlay() override; virtual void Tick(float DeltaSeconds) override; diff --git a/Source/Carla/SceneCaptureCamera.cpp b/Source/Carla/SceneCaptureCamera.cpp index dd6f89f71..4785285d3 100644 --- a/Source/Carla/SceneCaptureCamera.cpp +++ b/Source/Carla/SceneCaptureCamera.cpp @@ -20,7 +20,7 @@ ASceneCaptureCamera::ASceneCaptureCamera(const FObjectInitializer& ObjectInitial SizeY(200u), PostProcessEffect(EPostProcessEffect::None) { - PrimaryActorTick.bCanEverTick = true; + PrimaryActorTick.bCanEverTick = true; /// @todo Does it need to tick? PrimaryActorTick.TickGroup = TG_PrePhysics; MeshComp = CreateDefaultSubobject(TEXT("CamMesh0")); @@ -83,16 +83,6 @@ void ASceneCaptureCamera::BeginPlay() GameMode->RegisterCaptureCamera(*this); } -void ASceneCaptureCamera::Tick(float Delta) -{ - Super::Tick(Delta); - // Update the image bitmap. - FTextureRenderTargetResource* RTResource = CaptureRenderTarget->GameThread_GetRenderTargetResource(); - FReadSurfaceDataFlags ReadPixelFlags(RCM_UNorm); - ReadPixelFlags.SetLinearToGamma(true); - RTResource->ReadPixels(ImageBitMap, ReadPixelFlags); -} - FString ASceneCaptureCamera::GetPostProcessEffectAsString() const { const UEnum* ptr = FindObject(ANY_PACKAGE, TEXT("EPostProcessEffect"), true); @@ -101,6 +91,14 @@ FString ASceneCaptureCamera::GetPostProcessEffectAsString() const return ptr->GetEnumName(static_cast(PostProcessEffect)); } +bool ASceneCaptureCamera::ReadPixels(TArray &BitMap) const +{ + FTextureRenderTargetResource* RTResource = CaptureRenderTarget->GameThread_GetRenderTargetResource(); + FReadSurfaceDataFlags ReadPixelFlags(RCM_UNorm); + ReadPixelFlags.SetLinearToGamma(true); + return RTResource->ReadPixels(BitMap, ReadPixelFlags); +} + void ASceneCaptureCamera::UpdateDrawFrustum() { if(DrawFrustum && CaptureComponent2D) diff --git a/Source/Carla/SceneCaptureCamera.h b/Source/Carla/SceneCaptureCamera.h index 0932b58d3..3b1b4bf43 100644 --- a/Source/Carla/SceneCaptureCamera.h +++ b/Source/Carla/SceneCaptureCamera.h @@ -41,8 +41,6 @@ public: virtual void BeginPlay() override; - virtual void Tick(float Delta) override; - uint32 GetImageSizeX() const { return SizeX; @@ -60,10 +58,7 @@ public: FString GetPostProcessEffectAsString() const; - const TArray &GetImage() const - { - return ImageBitMap; - } + bool ReadPixels(TArray &BitMap) const; private: @@ -95,7 +90,4 @@ private: /** Scene capture component. */ UPROPERTY(EditAnywhere) class USceneCaptureComponent2D* CaptureComponent2D; - - UPROPERTY() - TArray ImageBitMap; }; From 6c93056fd71c7394da2b9646e5866f1a3c9378e2 Mon Sep 17 00:00:00 2001 From: nsubiron Date: Fri, 24 Mar 2017 14:10:05 +0100 Subject: [PATCH 3/3] Update to the new server API --- Source/Carla/Carla.cpp | 1 + Source/Carla/Carla.h | 1 + Source/Carla/Game/CarlaGameController.cpp | 163 +++++++++++++--------- Source/Carla/Game/CarlaGameController.h | 5 +- 4 files changed, 102 insertions(+), 68 deletions(-) diff --git a/Source/Carla/Carla.cpp b/Source/Carla/Carla.cpp index 7408dec99..ad9045142 100644 --- a/Source/Carla/Carla.cpp +++ b/Source/Carla/Carla.cpp @@ -5,6 +5,7 @@ #define LOCTEXT_NAMESPACE "FCarlaModule" DEFINE_LOG_CATEGORY(LogCarla); +DEFINE_LOG_CATEGORY(LogCarlaServer); void FCarlaModule::StartupModule() { diff --git a/Source/Carla/Carla.h b/Source/Carla/Carla.h index 95ba27af1..1e88aa873 100644 --- a/Source/Carla/Carla.h +++ b/Source/Carla/Carla.h @@ -9,6 +9,7 @@ #include "NonCopyable.h" DECLARE_LOG_CATEGORY_EXTERN(LogCarla, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(LogCarlaServer, Log, All); class FCarlaModule : public IModuleInterface { diff --git a/Source/Carla/Game/CarlaGameController.cpp b/Source/Carla/Game/CarlaGameController.cpp index 9e9067d68..f214d662a 100644 --- a/Source/Carla/Game/CarlaGameController.cpp +++ b/Source/Carla/Game/CarlaGameController.cpp @@ -11,8 +11,6 @@ #include -#define CSTEXT(text) TEXT("CarlaServer: " text) - // ============================================================================= // -- Set functions ------------------------------------------------------------ // ============================================================================= @@ -50,94 +48,111 @@ static void Set(std::vector &cImage, const TArray &BitMap) // -- Other static methods ----------------------------------------------------- // ============================================================================= +// Wait for the scene init to be sent, return false if we need to restart the +// server. +/// @todo At the moment we just ignored what it is sent. static bool ReadSceneInit(carla::CarlaServer &Server) { - carla::Mode mode; - uint32 scene; - UE_LOG(LogCarla, Log, CSTEXT("Waiting for tryReadSceneInit...")); - while (!Server.tryReadSceneInit(mode, scene)) { - if (Server.needsRestart()) + carla::Mode Mode; + uint32 Scene; + bool Success = false; + UE_LOG(LogCarlaServer, Log, TEXT("(tryReadSceneInit) Waiting for client...")); + while (!Success) { + if (!Server.tryReadSceneInit(Mode, Scene, Success)) return false; } return true; } +// Send the available start spots to the client and wait for them to answer. +// Return false if the server needs restart. static bool SendAndReadSceneValues( carla::CarlaServer &Server, const TArray &AvailableStartSpots, uint32 &StartIndex) { check(AvailableStartSpots.Num() > 0); - + // Retrieve the location of each player start. carla::Scene_Values sceneValues; sceneValues.possible_positions.reserve(AvailableStartSpots.Num()); for (APlayerStart *StartSpot : AvailableStartSpots) { check(StartSpot != nullptr); const FVector &Location = StartSpot->GetActorLocation(); - UE_LOG(LogCarla, Log, CSTEXT("Found start position {%f, %f}"), Location.X, Location.Y); sceneValues.possible_positions.push_back({Location.X, Location.Y}); } - // Send scene values. - UE_LOG(LogCarla, Log, CSTEXT("Send scene values: %d positions"), sceneValues.possible_positions.size()); - Server.sendSceneValues(sceneValues); - // Wait till we receive the answer. + // Send the positions. + /// @todo At the moment we don't send the cameras' projection matrices. + UE_LOG(LogCarlaServer, Log, TEXT("Sending %d available start positions"), sceneValues.possible_positions.size()); + if (!Server.sendSceneValues(sceneValues)) + return false; + // Wait till we receive an answer. uint32 EndIndex; - UE_LOG(LogCarla, Log, CSTEXT("Waiting for episode start...")); - while (!Server.tryReadEpisodeStart(StartIndex, EndIndex)) { - if (Server.needsRestart()) + bool Success = false; + UE_LOG(LogCarlaServer, Log, TEXT("(tryReadEpisodeStart) Waiting for client...")); + while (!Success) { + if (!Server.tryReadEpisodeStart(StartIndex, EndIndex, Success)) return false; } - UE_LOG(LogCarla, Log, CSTEXT("Episode start received: %d -> %d"), StartIndex, EndIndex); + UE_LOG(LogCarlaServer, Log, TEXT("Episode start received: { StartIndex = %d, EndIndex = %d }"), StartIndex, EndIndex); // Make sure the index is in range. if (StartIndex >= AvailableStartSpots.Num()) { - UE_LOG(LogCarla, Warning, CSTEXT("Received invalid start index, using zero instead")); + UE_LOG( + LogCarlaServer, + Error, + TEXT("Requested start position #%d but we only have %d, using first position instead"), + StartIndex, + AvailableStartSpots.Num()); StartIndex = 0u; } return true; } -static void SendReward( +static bool SendReward( carla::CarlaServer &Server, const ACarlaPlayerState &PlayerState) { - carla::Reward_Values reward; - reward.timestamp = PlayerState.GetTimeStamp(); - Set(reward.player_location, PlayerState.GetLocation()); - Set(reward.player_orientation, PlayerState.GetOrientation()); - Set(reward.player_acceleration, PlayerState.GetAcceleration()); - Set(reward.forward_speed, PlayerState.GetForwardSpeed()); - Set(reward.collision_car, PlayerState.GetCollisionIntensityCars()); - Set(reward.collision_pedestrian, PlayerState.GetCollisionIntensityPedestrians()); - Set(reward.collision_general, PlayerState.GetCollisionIntensityOther()); - Set(reward.intersect_other_lane, PlayerState.GetOtherLaneIntersectionFactor()); - Set(reward.intersect_offroad, PlayerState.GetOffRoadIntersectionFactor()); + auto reward = std::make_unique(); + reward->timestamp = PlayerState.GetTimeStamp(); + Set(reward->player_location, PlayerState.GetLocation()); + Set(reward->player_orientation, PlayerState.GetOrientation()); + Set(reward->player_acceleration, PlayerState.GetAcceleration()); + Set(reward->forward_speed, PlayerState.GetForwardSpeed()); + Set(reward->collision_car, PlayerState.GetCollisionIntensityCars()); + Set(reward->collision_pedestrian, PlayerState.GetCollisionIntensityPedestrians()); + Set(reward->collision_general, PlayerState.GetCollisionIntensityOther()); + Set(reward->intersect_other_lane, PlayerState.GetOtherLaneIntersectionFactor()); + Set(reward->intersect_offroad, PlayerState.GetOffRoadIntersectionFactor()); { // Add images. using CPS = ACarlaPlayerState; auto &ImageRGB0 = PlayerState.GetImage(CPS::ImageRGB0); if (ImageRGB0.BitMap.Num() > 0) { // Do not add any camera if first is invalid, also assume all the images // have the same size. - reward.image_width = ImageRGB0.SizeX; - reward.image_height = ImageRGB0.SizeY; - Set(reward.image_rgb_0, ImageRGB0.BitMap); - Set(reward.image_rgb_1, PlayerState.GetImage(CPS::ImageRGB1).BitMap); - Set(reward.image_depth_0, PlayerState.GetImage(CPS::ImageDepth0).BitMap); - Set(reward.image_depth_1, PlayerState.GetImage(CPS::ImageDepth1).BitMap); + reward->image_width = ImageRGB0.SizeX; + reward->image_height = ImageRGB0.SizeY; + Set(reward->image_rgb_0, ImageRGB0.BitMap); + Set(reward->image_rgb_1, PlayerState.GetImage(CPS::ImageRGB1).BitMap); + Set(reward->image_depth_0, PlayerState.GetImage(CPS::ImageDepth0).BitMap); + Set(reward->image_depth_1, PlayerState.GetImage(CPS::ImageDepth1).BitMap); } } - UE_LOG(LogCarla, Log, CSTEXT("Sending reward")); - Server.sendReward(reward); + UE_LOG(LogCarlaServer, Log, TEXT("Sending reward")); + return Server.sendReward(reward.release()); } -static void TryReadControl(carla::CarlaServer &Server, ACarlaVehicleController &Player) +static bool TryReadControl(carla::CarlaServer &Server, ACarlaVehicleController &Player) { - float steer; - float throttle; - if (Server.tryReadControl(steer, throttle)) { - Player.SetSteeringInput(steer); - Player.SetThrottleInput(throttle); - UE_LOG(LogCarla, Log, CSTEXT("Read control: steer = %f, throttle = %f"), steer, throttle); + float Steer; + float Throttle; + bool Success = false; + bool bServerNeedsRestart = Server.tryReadControl(Steer, Throttle, Success); + if (Success) { + check(!bServerNeedsRestart); + UE_LOG(LogCarlaServer, Log, TEXT("Read control: { Steer = %f, Throttle = %f }"), Steer, Throttle); + Player.SetSteeringInput(Steer); + Player.SetThrottleInput(Throttle); } + return bServerNeedsRestart; } // ============================================================================= @@ -150,18 +165,17 @@ CarlaGameController::CarlaGameController() : CarlaGameController::~CarlaGameController() { - UE_LOG(LogCarla, Log, CSTEXT("Destroying CarlaGameController...")); + UE_LOG(LogCarlaServer, Log, TEXT("Destroying CarlaGameController...")); } void CarlaGameController::Initialize() { if (bServerNeedsRestart) { - UE_LOG(LogCarla, Log, CSTEXT("Initializing CarlaServer")); - Server->init(1u); - if (ReadSceneInit(*Server)) { + UE_LOG(LogCarlaServer, Log, TEXT("Initializing CarlaServer")); + if (Server->init(1u) && ReadSceneInit(*Server)) { bServerNeedsRestart = false; } else { - UE_LOG(LogCarla, Warning, CSTEXT("Read scene init failed, server needs restart")); + UE_LOG(LogCarlaServer, Warning, TEXT("Failed to initialize, server needs restart")); } } } @@ -171,7 +185,7 @@ APlayerStart *CarlaGameController::ChoosePlayerStart( { uint32 StartIndex; if (!SendAndReadSceneValues(*Server, AvailableStartSpots, StartIndex)) { - UE_LOG(LogCarla, Warning, CSTEXT("Read scene values failed, server needs restart")); + UE_LOG(LogCarlaServer, Warning, TEXT("Unable to choose start position, server needs restart")); StartIndex = 0u; } return AvailableStartSpots[StartIndex]; @@ -185,30 +199,45 @@ void CarlaGameController::RegisterPlayer(AController &NewPlayer) void CarlaGameController::BeginPlay() { - Server->sendEndReset(); + if (!bServerNeedsRestart) { + UE_LOG(LogCarlaServer, Log, TEXT("Ready to play, notifying client")); + if (!Server->sendEndReset()) { + bServerNeedsRestart = true; + UE_LOG(LogCarlaServer, Warning, TEXT("Unable to send end reset, server needs restart")); + } + } } void CarlaGameController::Tick(float DeltaSeconds) { check(Player != nullptr); - if (bServerNeedsRestart || Server->needsRestart()) { - RestartLevel(true); - return; + if (bServerNeedsRestart || Server->needsRestart() || !TickServer()) { + UE_LOG(LogCarlaServer, Warning, TEXT("Client disconnected, server needs restart")); + bServerNeedsRestart = true; + RestartLevel(); } - - if (Server->newEpisodeRequested()) { - UE_LOG(LogCarla, Log, CSTEXT("New episode requested")); - RestartLevel(false); - return; - } - - SendReward(*Server, Player->GetPlayerState()); - TryReadControl(*Server, *Player); } -void CarlaGameController::RestartLevel(bool ServerNeedsRestart) +bool CarlaGameController::TickServer() { - UE_LOG(LogCarla, Log, CSTEXT("Restarting...")); - bServerNeedsRestart = ServerNeedsRestart; + // Check if the client requested a new episode. + bool bNewEpisodeRequested = false; + if (!Server->newEpisodeRequested(bNewEpisodeRequested)) { + return false; + } else if (bNewEpisodeRequested) { + UE_LOG(LogCarlaServer, Log, TEXT("New episode requested")); + RestartLevel(); + return true; + } + + // Send reward and try to read control. + return + SendReward(*Server, Player->GetPlayerState()) && + TryReadControl(*Server, *Player); +} + +void CarlaGameController::RestartLevel() +{ + UE_LOG(LogCarlaServer, Log, TEXT("Restarting the level...")); Player->RestartLevel(); } diff --git a/Source/Carla/Game/CarlaGameController.h b/Source/Carla/Game/CarlaGameController.h index 9760f1703..6e091468a 100644 --- a/Source/Carla/Game/CarlaGameController.h +++ b/Source/Carla/Game/CarlaGameController.h @@ -31,7 +31,10 @@ public: private: - void RestartLevel(bool ServerNeedsRestart); + /// Return false if the server needs restart. + bool TickServer(); + + void RestartLevel(); TUniquePtr Server;