CARLA
Loading...
Searching...
No Matches
test_multigpu_sensor_sync.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB).
2// This work is licensed under the terms of the MIT license.
3// For a copy, see <https://opensource.org/licenses/MIT>.
4
5#include "test.h"
6
9
10#include <cstring>
11#include <random>
12#include <sstream>
13#include <string>
14#include <vector>
15
22
23namespace {
24
25 /// The payload of the single packet in @a bytes, and its declared size.
26 std::string Payload(const std::string &bytes, uint32_t &size) {
27 std::memcpy(&size, bytes.data() + 1, sizeof(size));
28 return bytes.substr(carla::multigpu::kFrameDataHeaderSize);
29 }
30
31} // namespace
32
33TEST(MultiGpuSensorSync, ticks_round_trip_with_bit_exact_deltas) {
34 SensorTicks sent;
35 sent.ticks.push_back(SensorTick{7u, 0.05f});
36 sent.ticks.push_back(SensorTick{9u, 0.1f + 1e-7f});
37 std::ostringstream out;
38 sent.Write(out);
39 const std::string bytes = out.str();
40 ASSERT_EQ(static_cast<uint8_t>(bytes[0]), SensorTicks::kPacketId);
41
42 uint32_t size = 0u;
43 std::istringstream in(Payload(bytes, size));
44 SensorTicks got;
45 ASSERT_TRUE(got.Read(in, size));
46 ASSERT_EQ(got.ticks.size(), 2u);
47 EXPECT_EQ(got.ticks[0].actor_id, 7u);
48 EXPECT_EQ(got.ticks[0].delta_seconds, 0.05f);
49 EXPECT_EQ(got.ticks[1].delta_seconds, 0.1f + 1e-7f);
50}
51
52TEST(MultiGpuSensorSync, empty_ticks_are_still_a_packet) {
53 SensorTicks sent;
54 std::ostringstream out;
55 sent.Write(out);
56 uint32_t size = 0u;
57 std::istringstream in(Payload(out.str(), size));
58 SensorTicks got;
59 got.ticks.resize(3u);
60 ASSERT_TRUE(got.Read(in, size));
61 EXPECT_TRUE(got.ticks.empty());
62}
63
64TEST(MultiGpuSensorSync, states_round_trip_every_field) {
65 SensorStates sent;
66 SensorState lidar;
67 lidar.actor_id = 3u;
68 lidar.random_engine = 123456789u;
69 lidar.lidar_horizontal_angle = 2.5f;
70 SensorState imu;
71 imu.actor_id = 4u;
72 imu.random_engine = 42u;
74 history.older_location = {1.0, 2.0, 3.0};
75 history.last_location = {4.0, 5.0, 6.000001};
76 history.last_delta_seconds = 0.05f;
77 imu.imu_history = history;
78 imu.imu_local_rotation = std::array<double, 4>{0.0, -0.25881904510252074, 0.0, 0.96592582628906831};
79 SensorState radar;
80 radar.actor_id = 5u;
81 radar.radar_previous_location = std::array<double, 3>{-10.5, 20.25, 0.125};
82 sent.states = {lidar, imu, radar};
83
84 std::ostringstream out;
85 sent.Write(out);
86 uint32_t size = 0u;
87 std::istringstream in(Payload(out.str(), size));
88 SensorStates got;
89 ASSERT_TRUE(got.Read(in, size));
90 ASSERT_EQ(got.states.size(), 3u);
91 EXPECT_EQ(got.states[0].random_engine, lidar.random_engine);
92 EXPECT_EQ(got.states[0].lidar_horizontal_angle, lidar.lidar_horizontal_angle);
93 EXPECT_FALSE(got.states[0].imu_history.has_value());
94 ASSERT_TRUE(got.states[1].imu_history.has_value());
95 EXPECT_EQ(got.states[1].imu_history->last_location[2], 6.000001);
96 EXPECT_EQ(got.states[1].imu_history->last_delta_seconds, 0.05f);
97 ASSERT_TRUE(got.states[1].imu_local_rotation.has_value());
98 EXPECT_EQ(*got.states[1].imu_local_rotation, *imu.imu_local_rotation);
99 EXPECT_FALSE(got.states[0].imu_local_rotation.has_value());
100 ASSERT_TRUE(got.states[2].radar_previous_location.has_value());
101 EXPECT_EQ((*got.states[2].radar_previous_location)[1], 20.25);
102 EXPECT_FALSE(got.states[2].random_engine.has_value());
103}
104
105TEST(MultiGpuSensorSync, unknown_and_longer_fields_are_skipped) {
106 // One record: id 8, an unknown kind 9, then a random engine field that grew.
107 std::ostringstream record;
108 const uint32_t id = 8u;
109 record.write(reinterpret_cast<const char *>(&id), sizeof(id));
110 carla::multigpu::WriteField(record, 9u, std::string("future"));
111 std::string engine(sizeof(uint32_t) + 2u, '\0');
112 const uint32_t state = 777u;
113 std::memcpy(engine.data(), &state, sizeof(state));
115 std::ostringstream payload;
116 const uint32_t count = 1u;
117 payload.write(reinterpret_cast<const char *>(&count), sizeof(count));
118 carla::multigpu::WriteSizedBytes(payload, record.str());
119
120 const std::string body = payload.str();
121 std::istringstream in(body);
122 SensorStates got;
123 ASSERT_TRUE(got.Read(in, static_cast<uint32_t>(body.size())));
124 ASSERT_EQ(got.states.size(), 1u);
125 EXPECT_EQ(got.states[0].actor_id, 8u);
126 EXPECT_EQ(got.states[0].random_engine, std::optional<uint32_t>(777u));
127}
128
129TEST(MultiGpuSensorSync, truncated_records_fail) {
130 SensorStates sent;
131 SensorState radar;
132 radar.actor_id = 5u;
133 radar.radar_previous_location = std::array<double, 3>{1.0, 2.0, 3.0};
134 sent.states = {radar};
135 std::ostringstream out;
136 sent.Write(out);
137 uint32_t size = 0u;
138 const std::string payload = Payload(out.str(), size);
139 const std::string cut = payload.substr(0u, payload.size() - 4u);
140 std::istringstream in(cut);
141 SensorStates got;
142 EXPECT_FALSE(got.Read(in, static_cast<uint32_t>(cut.size())));
143}
144
145TEST(MultiGpuSensorSync, minstd_resumes_the_same_draws) {
146 // A mirror created later continues the primary sensor's noise exactly.
147 std::minstd_rand primary(1234u);
148 for (int i = 0; i < 17; ++i) {
149 (void)std::uniform_real_distribution<float>()(primary);
150 }
151 std::minstd_rand mirror(99u);
152 ASSERT_TRUE(SetEngineState(mirror, GetEngineState(primary)));
153 for (int i = 0; i < 50; ++i) {
154 EXPECT_EQ(std::uniform_real_distribution<float>()(mirror), std::uniform_real_distribution<float>()(primary));
155 EXPECT_EQ(std::normal_distribution<float>(0.0f, 2.0f)(mirror), std::normal_distribution<float>(0.0f, 2.0f)(primary));
156 EXPECT_EQ(std::uniform_int_distribution<int32_t>(0, 1000)(mirror), std::uniform_int_distribution<int32_t>(0, 1000)(primary));
157 }
158}
159
160TEST(MultiGpuSensorSync, impossible_engine_states_are_refused) {
161 std::minstd_rand engine;
162 EXPECT_FALSE(SetEngineState(engine, 0u));
163 EXPECT_FALSE(SetEngineState(engine, 2147483647u));
164 EXPECT_TRUE(SetEngineState(engine, 1u));
165}
uint32_t GetEngineState(const std::minstd_rand &engine)
The state of a minstd_rand: the value its next draw is computed from.
Definition sensorSync.h:254
constexpr std::size_t kFrameDataHeaderSize
Frame data (the SEND_FRAME payload) is a sequence of packets [uint8 id][uint32 payload size][payload]...
bool SetEngineState(std::minstd_rand &engine, uint32_t state)
Makes engine continue from state, as GetEngineState returned it.
Definition sensorSync.h:264
void WriteField(std::ostream &out, uint8_t kind, const std::string &value)
void WriteSizedBytes(std::ostream &out, const std::string &bytes)
One [u32 size][bytes] entry: a variable-size record or, after a kind byte, a TLV value.
An IMU's history: its two previous locations (world, cm) and the delta between them; its acceleromete...
Definition sensorSync.h:77
std::array< double, 3 > last_location
Definition sensorSync.h:79
std::array< double, 3 > older_location
Definition sensorSync.h:78
One record of frame data packet 27: the runtime state a sensor of the primary starts this frame's tic...
Definition sensorSync.h:87
std::optional< ImuHistory > imu_history
Definition sensorSync.h:93
std::optional< float > lidar_horizontal_angle
Ray-cast lidars: where this tick's sweep starts (radians).
Definition sensorSync.h:92
std::optional< std::array< double, 4 > > imu_local_rotation
IMU: its rotation relative to its parent (x, y, z, w), which turns the parent's angular velocity into...
Definition sensorSync.h:99
std::optional< std::array< double, 3 > > radar_previous_location
Radar: its previous location (world, cm), for its own velocity.
Definition sensorSync.h:95
std::optional< uint32_t > random_engine
URandomEngine (noise, lidar drop-off, radar ray directions).
Definition sensorSync.h:90
Frame data packet 27, for the sensors of packet 26 that carry state.
Definition sensorSync.h:108
std::vector< SensorState > states
Definition sensorSync.h:121
void Write(std::ostream &out) const
Definition sensorSync.h:123
bool Read(std::istream &in, uint32_t size)
Definition sensorSync.h:159
One record of frame data packet 26: a sensor of the primary that ticks in this frame,...
Definition sensorSync.h:24
Frame data packet 26: every sensor of the primary that ticks in this frame.
Definition sensorSync.h:34
static constexpr uint8_t kPacketId
Definition sensorSync.h:36
std::vector< SensorTick > ticks
Definition sensorSync.h:40
void Write(std::ostream &out) const
Definition sensorSync.h:42
bool Read(std::istream &in, uint32_t size)
Definition sensorSync.h:52
TEST(MultiGpuSensorSync, ticks_round_trip_with_bit_exact_deltas)