CARLA
Loading...
Searching...
No Matches
test_multigpu_mirror.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 <algorithm>
11#include <cstring>
12#include <sstream>
13#include <string>
14#include <unordered_map>
15#include <vector>
16
20
21namespace {
22
23 /// Reads back the payload of the single packet in @a bytes.
24 bool ReadPositions(const std::string &bytes, MirroredPositions &out) {
25 uint32_t size = 0u;
26 std::memcpy(&size, bytes.data() + 1, sizeof(size));
27 std::istringstream in(bytes.substr(carla::multigpu::kFrameDataHeaderSize));
28 return out.Read(in, size);
29 }
30
31} // namespace
32
33TEST(MultiGpuMirror, positions_round_trip_bit_exact) {
35 MirroredPosition camera;
36 camera.id = 7u;
37 camera.location[0] = 12345.678901234;
38 camera.location[1] = -0.000001;
39 camera.location[2] = 250.0;
40 // Looking straight down: the pose an Euler round trip cannot keep.
41 camera.rotation[0] = 0.0;
42 camera.rotation[1] = 0.70710678118654757;
43 camera.rotation[2] = 0.0;
44 camera.rotation[3] = 0.70710678118654746;
45 sent.positions.push_back(camera);
46 MirroredPosition other;
47 other.id = 9u;
48 sent.positions.push_back(other);
49
50 std::ostringstream out;
51 sent.Write(out);
52 const std::string bytes = out.str();
53 ASSERT_EQ(static_cast<uint8_t>(bytes[0]), MirroredPositions::kPacketId);
55
57 ASSERT_TRUE(ReadPositions(bytes, got));
58 ASSERT_EQ(got.positions.size(), 2u);
59 EXPECT_EQ(got.positions[0].id, 7u);
60 for (int i = 0; i < 3; ++i) {
61 EXPECT_EQ(got.positions[0].location[i], camera.location[i]);
62 }
63 for (int i = 0; i < 4; ++i) {
64 EXPECT_EQ(got.positions[0].rotation[i], camera.rotation[i]);
65 }
66 EXPECT_EQ(got.positions[1].id, 9u);
67}
68
69TEST(MultiGpuMirror, positions_from_a_newer_build_keep_their_known_fields) {
70 // Records of 64 bytes: v1's 60 plus a field this build does not know.
71 std::ostringstream payload;
72 const uint32_t count = 2u;
73 const uint16_t record_size = 64u;
74 payload.write(reinterpret_cast<const char *>(&count), sizeof(count));
75 payload.write(reinterpret_cast<const char *>(&record_size), sizeof(record_size));
76 for (uint32_t id = 1u; id <= count; ++id) {
77 payload.write(reinterpret_cast<const char *>(&id), sizeof(id));
78 const double values[7] = {double(id), 2.0, 3.0, 0.0, 0.0, 0.0, 1.0};
79 payload.write(reinterpret_cast<const char *>(values), sizeof(values));
80 const uint32_t extra = 0xFFFFFFFFu;
81 payload.write(reinterpret_cast<const char *>(&extra), sizeof(extra));
82 }
83 const std::string body = payload.str();
84 std::istringstream in(body);
86 ASSERT_TRUE(got.Read(in, static_cast<uint32_t>(body.size())));
87 ASSERT_EQ(got.positions.size(), 2u);
88 EXPECT_EQ(got.positions[1].id, 2u);
89 EXPECT_EQ(got.positions[1].location[0], 2.0);
90}
91
92TEST(MultiGpuMirror, truncated_positions_fail) {
94 sent.positions.resize(3u);
95 std::ostringstream out;
96 sent.Write(out);
97 const std::string bytes = out.str();
98 const std::string body = bytes.substr(carla::multigpu::kFrameDataHeaderSize, 6u + 100u);
99 std::istringstream in(body);
101 EXPECT_FALSE(got.Read(in, static_cast<uint32_t>(body.size())));
102}
103
104TEST(MultiGpuMirror, order_parents_first_depth_two) {
105 // 3 is attached to 2, which is attached to 1; 4 is a root.
106 const std::vector<uint32_t> ids = {3u, 2u, 4u, 1u};
107 const std::unordered_map<uint32_t, uint32_t> parents = {{3u, 2u}, {2u, 1u}};
108 const std::vector<uint32_t> order = OrderParentsFirst(ids, parents);
109 EXPECT_EQ(order, (std::vector<uint32_t>{4u, 1u, 2u, 3u}));
110}
111
112TEST(MultiGpuMirror, missing_parent_and_cycle_are_guarded) {
113 // 5's parent is not in the frame; 6 and 7 are each other's parent.
114 const std::vector<uint32_t> ids = {5u, 6u, 7u, 8u};
115 const std::unordered_map<uint32_t, uint32_t> parents = {{5u, 99u}, {6u, 7u}, {7u, 6u}, {8u, 7u}};
116 const std::vector<uint32_t> order = OrderParentsFirst(ids, parents);
117 ASSERT_EQ(order.size(), ids.size());
118 // Every id once; the actor attached to the cycle comes after it.
119 const auto pos = [&](uint32_t id) {
120 return std::find(order.begin(), order.end(), id) - order.begin();
121 };
122 for (uint32_t id : ids) {
123 EXPECT_EQ(std::count(order.begin(), order.end(), id), 1);
124 }
125 EXPECT_LT(pos(7u), pos(8u));
126}
127
128namespace {
129
130 bool ReadParts(const std::string &bytes, carla::multigpu::MirroredVehicleParts &out) {
131 uint32_t size = 0u;
132 std::memcpy(&size, bytes.data() + 1, sizeof(size));
133 std::istringstream in(bytes.substr(carla::multigpu::kFrameDataHeaderSize));
134 return out.Read(in, size);
135 }
136
137} // namespace
138
139TEST(MultiGpuMirror, vehicle_parts_round_trip) {
142 car.actor_id = 21u;
143 car.wheels = {{12.5f, 361.25f, 3.5f}, {-12.5f, 0.125f, -1.0f}, {0.0f, 90.0f, 0.0f}, {0.0f, 180.0f, 2.25f}};
145 door.door = 1u;
146 door.location = {10.0, -85.5, 40.25};
147 door.rotation = {0.0, 0.0, 0.38268343236508978, 0.92387953251128674};
148 car.open_doors = {door};
149 car.current_gear = -1;
150 car.manual_gear_shift = true;
151 car.simulating_physics = true;
153 bike.actor_id = 22u;
154 bike.wheels = {{5.0f, 10.0f, 0.5f}, {0.0f, 10.0f, 0.25f}};
155 sent.vehicles = {car, bike};
156
157 std::ostringstream out;
158 sent.Write(out);
159 const std::string bytes = out.str();
160 ASSERT_EQ(static_cast<uint8_t>(bytes[0]), carla::multigpu::MirroredVehicleParts::kPacketId);
162 ASSERT_TRUE(ReadParts(bytes, got));
163 ASSERT_EQ(got.vehicles.size(), 2u);
164 ASSERT_EQ(got.vehicles[0].wheels.size(), 4u);
165 EXPECT_EQ(got.vehicles[0].wheels[1].steer, -12.5f);
166 EXPECT_EQ(got.vehicles[0].wheels[0].spin, 361.25f);
167 EXPECT_EQ(got.vehicles[0].wheels[3].suspension_offset, 2.25f);
168 ASSERT_EQ(got.vehicles[0].open_doors.size(), 1u);
169 EXPECT_EQ(got.vehicles[0].open_doors[0].door, 1u);
170 EXPECT_EQ(got.vehicles[0].open_doors[0].location, door.location);
171 EXPECT_EQ(got.vehicles[0].open_doors[0].rotation, door.rotation);
172 EXPECT_EQ(got.vehicles[0].current_gear, std::optional<int32_t>(-1));
173 EXPECT_TRUE(got.vehicles[0].manual_gear_shift);
174 EXPECT_TRUE(got.vehicles[0].simulating_physics);
175 EXPECT_EQ(got.vehicles[1].actor_id, 22u);
176 EXPECT_EQ(got.vehicles[1].wheels.size(), 2u);
177 EXPECT_TRUE(got.vehicles[1].open_doors.empty());
178 EXPECT_FALSE(got.vehicles[1].current_gear.has_value());
179}
180
181TEST(MultiGpuMirror, vehicle_parts_from_a_newer_build_keep_their_known_fields) {
182 // One vehicle: wheel records of 16 bytes (v1's 12 and a new float) and an
183 // unknown field.
184 std::ostringstream wheels;
185 const uint32_t count = 1u;
186 const uint16_t record_size = 16u;
187 wheels.write(reinterpret_cast<const char *>(&count), sizeof(count));
188 wheels.write(reinterpret_cast<const char *>(&record_size), sizeof(record_size));
189 const float values[4] = {1.0f, 2.0f, 3.0f, 99.0f};
190 wheels.write(reinterpret_cast<const char *>(values), sizeof(values));
191 std::ostringstream record;
192 const uint32_t id = 5u;
193 record.write(reinterpret_cast<const char *>(&id), sizeof(id));
194 carla::multigpu::WriteField(record, 9u, std::string("future"));
196 std::ostringstream payload;
197 payload.write(reinterpret_cast<const char *>(&count), sizeof(count));
198 carla::multigpu::WriteSizedBytes(payload, record.str());
199
200 const std::string body = payload.str();
201 std::istringstream in(body);
203 ASSERT_TRUE(got.Read(in, static_cast<uint32_t>(body.size())));
204 ASSERT_EQ(got.vehicles.size(), 1u);
205 ASSERT_EQ(got.vehicles[0].wheels.size(), 1u);
206 EXPECT_EQ(got.vehicles[0].wheels[0].suspension_offset, 3.0f);
207}
208
209TEST(MultiGpuMirror, truncated_vehicle_parts_fail) {
212 car.actor_id = 1u;
213 car.wheels.resize(4u);
214 car.open_doors.resize(2u);
215 sent.vehicles = {car};
216 std::ostringstream out;
217 sent.Write(out);
218 const std::string bytes = out.str();
219 const std::string body = bytes.substr(carla::multigpu::kFrameDataHeaderSize, bytes.size() - carla::multigpu::kFrameDataHeaderSize - 10u);
220 std::istringstream in(body);
222 EXPECT_FALSE(got.Read(in, static_cast<uint32_t>(body.size())));
223}
constexpr std::size_t kFrameDataHeaderSize
Frame data (the SEND_FRAME payload) is a sequence of packets [uint8 id][uint32 payload size][payload]...
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.
std::vector< uint32_t > OrderParentsFirst(const std::vector< uint32_t > &ids, const std::unordered_map< uint32_t, uint32_t > &parents)
Order in which to place actors so that every parent is placed before its children: by attachment dept...
An open door, which on the primary is a physics body of its own: its pose relative to the vehicle,...
std::array< double, 3 > location
std::array< double, 4 > rotation
One record of frame data packet 34: an actor's world pose as the primary holds it,...
Frame data packet 34: every live actor's pose in this frame.
bool Read(std::istream &in, uint32_t size)
Reads a payload of size bytes; records that grew in a newer build are read up to the fields known her...
void Write(std::ostream &out) const
static constexpr uint16_t kRecordSizeV1
static constexpr uint8_t kPacketId
std::vector< MirroredPosition > positions
Frame data packet 32, every frame: the moving parts of every vehicle.
static constexpr uint8_t kPacketId
std::vector< VehicleParts > vehicles
void Write(std::ostream &out) const
bool Read(std::istream &in, uint32_t size)
A vehicle's moving parts: every wheel, by wheel index, and the doors open (any other door is closed),...
std::vector< DoorPose > open_doors
std::vector< WheelPose > wheels
std::optional< int32_t > current_gear
bool simulating_physics
Whether the primary's body simulates physics (it reports no angular velocity otherwise).
TEST(MultiGpuMirror, positions_round_trip_bit_exact)