CARLA
Loading...
Searching...
No Matches
test_multigpu_sensor_streams.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
7#include <carla/Buffer.h>
8#include <carla/BufferView.h>
14
15#include <boost/asio/io_context.hpp>
16#include <boost/asio/ip/tcp.hpp>
17#include <boost/asio/write.hpp>
18
19#include <algorithm>
20#include <array>
21#include <atomic>
22#include <chrono>
23#include <map>
24#include <optional>
25#include <set>
26#include <string>
27#include <thread>
28
29namespace {
30
34
35 stream_id_type StreamIdOf(const carla::streaming::Stream &stream) {
36 return token_type(stream.token()).get_stream_id();
37 }
38
39 /// A secondary's streaming server, routing sessions by primary stream id.
40 struct SecondaryStreams {
41 SecondaryStreams() {
42 server.SetSessionsUseStreamAliases(true);
43 server.AsyncRun(1u);
44 registry.OpenEpisode(0u);
45 }
46
48 SensorStreamRegistry registry;
49 };
50
51 void WriteMessage(carla::streaming::Stream &stream, const std::string &text) {
52 carla::Buffer buffer(boost::asio::buffer(text.c_str(), text.size()));
53 stream.Write(carla::BufferView::CreateFrom(std::move(buffer)));
54 }
55
56 /// Writes to @a stream until @a received is set or @a attempts writes
57 /// (10 ms apart) pass.
58 bool WriteUntilReceived(
60 const std::atomic_bool &received,
61 int attempts = 200) {
62 for (int attempt = 0; attempt < attempts && !received; ++attempt) {
63 WriteMessage(stream, "data");
64 std::this_thread::sleep_for(std::chrono::milliseconds(10));
65 }
66 return received;
67 }
68
69 boost::asio::ip::tcp::socket OpenRawSession(
70 boost::asio::io_context &io_context,
71 uint16_t port,
72 stream_id_type stream_id) {
73 boost::asio::ip::tcp::socket socket(io_context);
74 socket.connect({boost::asio::ip::make_address("127.0.0.1"), port});
75 boost::asio::write(socket, boost::asio::buffer(&stream_id, sizeof(stream_id)));
76 return socket;
77 }
78
79 /// Writes to @a stream until @a socket has data or about two seconds pass.
80 bool WriteUntilReadable(carla::streaming::Stream &stream, boost::asio::ip::tcp::socket &socket) {
81 for (int attempt = 0; attempt < 200; ++attempt) {
82 WriteMessage(stream, "data");
83 std::this_thread::sleep_for(std::chrono::milliseconds(10));
84 boost::system::error_code ec;
85 if (socket.available(ec) > 0u) {
86 return true;
87 }
88 }
89 return false;
90 }
91
92 void Drain(boost::asio::ip::tcp::socket &socket) {
93 std::array<char, 1024> buffer;
94 boost::system::error_code ec;
95 while (socket.available(ec) > 0u) {
96 socket.read_some(boost::asio::buffer(buffer), ec);
97 }
98 }
99
100} // namespace
101
102TEST(streaming, find_token_does_not_create_a_stream_for_an_unknown_id) {
104 EXPECT_FALSE(server.FindToken(42u).has_value());
105 EXPECT_FALSE(server.FindToken(42u).has_value());
106 EXPECT_FALSE(server.IsEnabledForROS(42u));
107}
108
109TEST(streaming, find_token_returns_the_existing_stream_token) {
111 auto stream = server.MakeStream();
112 const auto token = server.FindToken(StreamIdOf(stream));
113 ASSERT_TRUE(token.has_value());
114 EXPECT_EQ(token->get_stream_id(), StreamIdOf(stream));
115}
116
117TEST(streaming, stream_alias_can_be_set_replaced_and_removed) {
119 EXPECT_FALSE(server.FindStreamAlias(100u).has_value());
120 server.SetStreamAlias(100u, 3u);
121 EXPECT_EQ(server.FindStreamAlias(100u), std::optional<stream_id_type>(3u));
122 server.SetStreamAlias(100u, 4u);
123 EXPECT_EQ(server.FindStreamAlias(100u), std::optional<stream_id_type>(4u));
124 server.RemoveStreamAlias(100u);
125 EXPECT_FALSE(server.FindStreamAlias(100u).has_value());
126}
127
128TEST(MultiGpuSensorStreams, token_carries_the_primary_stream_id_and_the_secondary_port) {
129 SecondaryStreams secondary;
130 auto sensor = secondary.server.MakeStream();
131 EXPECT_FALSE(secondary.registry.Bind(secondary.server, 7u, StreamIdOf(sensor), true).has_value());
132
133 const auto token = secondary.registry.Resolve(secondary.server, 7u, 100u);
134 ASSERT_TRUE(token.has_value());
135 EXPECT_EQ(token->get_stream_id(), 100u);
136 EXPECT_EQ(token->get_port(), secondary.server.GetLocalEndpoint().port());
137 EXPECT_EQ(secondary.server.FindStreamAlias(100u), std::optional<stream_id_type>(StreamIdOf(sensor)));
138}
139
140TEST(MultiGpuSensorStreams, resolve_is_keyed_by_actor_not_creation_order) {
141 SecondaryStreams secondary;
142 // The secondary replays sensors in a different order (e.g. a spawn frame
143 // was dropped and later recovered by a full resync).
144 auto second_sensor = secondary.server.MakeStream();
145 auto first_sensor = secondary.server.MakeStream();
146 EXPECT_FALSE(secondary.registry.Bind(secondary.server, 20u, StreamIdOf(second_sensor), true).has_value());
147 EXPECT_FALSE(secondary.registry.Bind(secondary.server, 10u, StreamIdOf(first_sensor), true).has_value());
148
149 ASSERT_TRUE(secondary.registry.Resolve(secondary.server, 10u, 101u).has_value());
150 ASSERT_TRUE(secondary.registry.Resolve(secondary.server, 20u, 102u).has_value());
151 EXPECT_EQ(secondary.server.FindStreamAlias(101u), std::optional<stream_id_type>(StreamIdOf(first_sensor)));
152 EXPECT_EQ(secondary.server.FindStreamAlias(102u), std::optional<stream_id_type>(StreamIdOf(second_sensor)));
153}
154
155TEST(MultiGpuSensorStreams, resolve_before_spawn_reserves_a_stream_the_sensor_adopts) {
156 SecondaryStreams secondary;
157 const auto token = secondary.registry.Resolve(secondary.server, 7u, 100u);
158 ASSERT_TRUE(token.has_value());
159 EXPECT_EQ(token->get_stream_id(), 100u);
160 const auto reserved_id = secondary.server.FindStreamAlias(100u);
161 ASSERT_TRUE(reserved_id.has_value());
162
163 const auto again = secondary.registry.Resolve(secondary.server, 7u, 100u);
164 ASSERT_TRUE(again.has_value());
165 EXPECT_EQ(again->get_stream_id(), 100u);
166 EXPECT_EQ(secondary.server.FindStreamAlias(100u), reserved_id);
167
168 auto sensor = secondary.server.MakeStream();
169 ASSERT_NE(StreamIdOf(sensor), *reserved_id);
170 const auto adopted = secondary.registry.Bind(secondary.server, 7u, StreamIdOf(sensor), true);
171 ASSERT_TRUE(adopted.has_value());
172 EXPECT_EQ(StreamIdOf(*adopted), *reserved_id);
173 EXPECT_EQ(secondary.server.FindStreamAlias(100u), reserved_id);
174}
175
176TEST(MultiGpuSensorStreams, resolve_after_the_sensor_stream_closed_reserves_a_new_one) {
177 SecondaryStreams secondary;
178 auto sensor = secondary.server.MakeStream();
179 EXPECT_FALSE(secondary.registry.Bind(secondary.server, 7u, StreamIdOf(sensor), true).has_value());
180 ASSERT_TRUE(secondary.registry.Resolve(secondary.server, 7u, 100u).has_value());
181 secondary.server.CloseStream(StreamIdOf(sensor));
182
183 const auto token = secondary.registry.Resolve(secondary.server, 7u, 100u);
184 ASSERT_TRUE(token.has_value());
185 EXPECT_EQ(token->get_stream_id(), 100u);
186 const auto reserved_id = secondary.server.FindStreamAlias(100u);
187 ASSERT_TRUE(reserved_id.has_value());
188 EXPECT_NE(*reserved_id, StreamIdOf(sensor));
189 EXPECT_TRUE(secondary.server.FindToken(*reserved_id).has_value());
190}
191
192TEST(MultiGpuSensorStreams, unbind_removes_the_alias_and_closes_an_unadopted_reservation) {
193 SecondaryStreams secondary;
194 ASSERT_TRUE(secondary.registry.Resolve(secondary.server, 7u, 100u).has_value());
195 const auto reserved_id = secondary.server.FindStreamAlias(100u);
196 ASSERT_TRUE(reserved_id.has_value());
197
198 secondary.registry.Unbind(secondary.server, 7u);
199 EXPECT_FALSE(secondary.server.FindStreamAlias(100u).has_value());
200 EXPECT_FALSE(secondary.server.FindToken(*reserved_id).has_value());
201}
202
203TEST(MultiGpuSensorStreams, episode_change_drops_aliases_and_reservations_but_not_sensor_streams) {
204 SecondaryStreams secondary;
205 auto sensor = secondary.server.MakeStream();
206 EXPECT_FALSE(secondary.registry.Bind(secondary.server, 7u, StreamIdOf(sensor), true).has_value());
207 ASSERT_TRUE(secondary.registry.Resolve(secondary.server, 7u, 100u).has_value());
208 ASSERT_TRUE(secondary.registry.Resolve(secondary.server, 8u, 101u).has_value());
209 const auto reserved_id = secondary.server.FindStreamAlias(101u);
210 ASSERT_TRUE(reserved_id.has_value());
211
212 static_cast<void>(secondary.registry.BeginEpisodeChange(secondary.server));
213 EXPECT_FALSE(secondary.server.FindStreamAlias(100u).has_value());
214 EXPECT_FALSE(secondary.server.FindStreamAlias(101u).has_value());
215 EXPECT_FALSE(secondary.server.FindToken(*reserved_id).has_value());
216 // The sensor's own stream belongs to the sensor, which closes it itself.
217 EXPECT_TRUE(secondary.server.FindToken(StreamIdOf(sensor)).has_value());
218}
219
220TEST(MultiGpuSensorStreams, bind_and_unbind_are_ignored_until_the_latest_load_opens) {
221 SecondaryStreams secondary;
222 const auto first_load = secondary.registry.BeginEpisodeChange(secondary.server);
223 const auto second_load = secondary.registry.BeginEpisodeChange(secondary.server);
224 ASSERT_NE(first_load, second_load);
225
226 // A GET_TOKEN during the load still gets a token right away.
227 ASSERT_TRUE(secondary.registry.Resolve(secondary.server, 7u, 100u).has_value());
228 const auto reserved_id = secondary.server.FindStreamAlias(100u);
229 ASSERT_TRUE(reserved_id.has_value());
230
231 // Replayed frames of the previous episode reuse its actor ids.
232 auto stale_sensor = secondary.server.MakeStream();
233 EXPECT_FALSE(secondary.registry.Bind(secondary.server, 7u, StreamIdOf(stale_sensor), true).has_value());
234 secondary.registry.Unbind(secondary.server, 7u);
235 secondary.registry.OpenEpisode(first_load);
236 EXPECT_FALSE(secondary.registry.Bind(secondary.server, 7u, StreamIdOf(stale_sensor), true).has_value());
237 EXPECT_EQ(secondary.server.FindStreamAlias(100u), reserved_id);
238 EXPECT_TRUE(secondary.server.FindToken(*reserved_id).has_value());
239
240 secondary.registry.OpenEpisode(second_load);
241 auto sensor = secondary.server.MakeStream();
242 const auto adopted = secondary.registry.Bind(secondary.server, 7u, StreamIdOf(sensor), true);
243 ASSERT_TRUE(adopted.has_value());
244 EXPECT_EQ(StreamIdOf(*adopted), *reserved_id);
245}
246
247TEST(MultiGpuSensorStreams, close_episode_stops_binding_but_keeps_reservations) {
248 SecondaryStreams secondary;
249 ASSERT_TRUE(secondary.registry.Resolve(secondary.server, 7u, 100u).has_value());
250 secondary.registry.CloseEpisode();
251
252 auto sensor = secondary.server.MakeStream();
253 EXPECT_FALSE(secondary.registry.Bind(secondary.server, 7u, StreamIdOf(sensor), true).has_value());
254 EXPECT_TRUE(secondary.server.FindStreamAlias(100u).has_value());
255}
256
257TEST(MultiGpuSensorStreams, secondaries_with_colliding_local_ids_hand_out_distinct_primary_ids) {
258 SecondaryStreams secondary_a;
259 SecondaryStreams secondary_b;
260 auto sensor_a = secondary_a.server.MakeStream();
261 auto sensor_b = secondary_b.server.MakeStream();
262 ASSERT_EQ(StreamIdOf(sensor_a), StreamIdOf(sensor_b));
263 EXPECT_FALSE(secondary_a.registry.Bind(secondary_a.server, 7u, StreamIdOf(sensor_a), true).has_value());
264 EXPECT_FALSE(secondary_b.registry.Bind(secondary_b.server, 8u, StreamIdOf(sensor_b), true).has_value());
265
266 const auto token_a = secondary_a.registry.Resolve(secondary_a.server, 7u, 14u);
267 const auto token_b = secondary_b.registry.Resolve(secondary_b.server, 8u, 15u);
268 ASSERT_TRUE(token_a.has_value());
269 ASSERT_TRUE(token_b.has_value());
270 EXPECT_EQ(token_a->get_stream_id(), 14u);
271 EXPECT_EQ(token_b->get_stream_id(), 15u);
272
273 // One client subscribed to both, as the Python client keys streams by id.
274 std::atomic_bool received_a{false};
275 std::atomic_bool received_b{false};
277 client.AsyncRun(1u);
278 client.Subscribe(*token_a, [&received_a](auto) { received_a = true; });
279 client.Subscribe(*token_b, [&received_b](auto) { received_b = true; });
280 EXPECT_TRUE(WriteUntilReceived(sensor_a, received_a));
281 EXPECT_TRUE(WriteUntilReceived(sensor_b, received_b));
282}
283
284TEST(MultiGpuSensorStreams, subscription_by_primary_id_reaches_the_adopted_stream_and_unsubscribes_by_it) {
285 SecondaryStreams secondary;
286 const auto token = secondary.registry.Resolve(secondary.server, 7u, 100u);
287 ASSERT_TRUE(token.has_value());
288
289 std::atomic_bool received{false};
290 std::atomic_size_t count{0u};
292 client.AsyncRun(1u);
293 client.Subscribe(*token, [&count, &received](auto) {
294 ++count;
295 received = true;
296 });
297
298 auto own_stream = secondary.server.MakeStream();
299 auto adopted = secondary.registry.Bind(secondary.server, 7u, StreamIdOf(own_stream), true);
300 ASSERT_TRUE(adopted.has_value());
301 secondary.server.CloseStream(StreamIdOf(own_stream));
302 ASSERT_TRUE(WriteUntilReceived(*adopted, received));
303
304 // The primary's own token for the sensor: same stream id, other port.
305 token_type primary_token(*token);
306 primary_token.set_stream_id(100u);
307 client.UnSubscribe(primary_token);
308 std::this_thread::sleep_for(std::chrono::milliseconds(100));
309 const auto count_after_unsubscribe = count.load();
310 for (int i = 0; i < 20; ++i) {
311 WriteMessage(*adopted, "data");
312 std::this_thread::sleep_for(std::chrono::milliseconds(5));
313 }
314 EXPECT_EQ(count.load(), count_after_unsubscribe);
315}
316
317TEST(MultiGpuSensorStreams, subscription_by_an_unaliased_id_is_refused) {
318 SecondaryStreams secondary;
319 auto sensor = secondary.server.MakeStream();
320 EXPECT_FALSE(secondary.registry.Bind(secondary.server, 7u, StreamIdOf(sensor), true).has_value());
321 const auto token = secondary.registry.Resolve(secondary.server, 7u, 100u);
322 ASSERT_TRUE(token.has_value());
323
324 // A stale id from another episode that happens to equal a local stream id.
325 token_type stale_token(*token);
326 stale_token.set_stream_id(StreamIdOf(sensor));
327 std::atomic_bool received{false};
329 client.AsyncRun(1u);
330 client.Subscribe(stale_token, [&received](auto) { received = true; });
331 for (int i = 0; i < 20; ++i) {
332 WriteMessage(sensor, "data");
333 std::this_thread::sleep_for(std::chrono::milliseconds(5));
334 }
335 EXPECT_FALSE(received);
336 client.UnSubscribe(stale_token);
337}
338
339TEST(MultiGpuSensorStreams, reused_sensor_keeps_its_stream_and_takes_over_the_reservation) {
340 SecondaryStreams secondary;
341 ASSERT_TRUE(secondary.registry.Resolve(secondary.server, 7u, 100u).has_value());
342 const auto reserved_id = secondary.server.FindStreamAlias(100u);
343 ASSERT_TRUE(reserved_id.has_value());
344
345 auto live_sensor = secondary.server.MakeStream();
346 EXPECT_FALSE(secondary.registry.Bind(secondary.server, 7u, StreamIdOf(live_sensor), false).has_value());
347 EXPECT_EQ(secondary.server.FindStreamAlias(100u), std::optional<stream_id_type>(StreamIdOf(live_sensor)));
348 EXPECT_FALSE(secondary.server.FindToken(*reserved_id).has_value());
349 EXPECT_TRUE(secondary.server.FindToken(StreamIdOf(live_sensor)).has_value());
350
351 const auto token = secondary.registry.Resolve(secondary.server, 7u, 100u);
352 ASSERT_TRUE(token.has_value());
353 EXPECT_EQ(secondary.server.FindStreamAlias(100u), std::optional<stream_id_type>(StreamIdOf(live_sensor)));
354}
355
356TEST(MultiGpuSensorStreams, early_subscriber_of_a_reused_sensor_reconnects_to_its_stream) {
357 SecondaryStreams secondary;
358 const auto token = secondary.registry.Resolve(secondary.server, 7u, 100u);
359 ASSERT_TRUE(token.has_value());
360
361 std::atomic_bool received{false};
363 client.AsyncRun(1u);
364 client.Subscribe(*token, [&received](auto) { received = true; });
365 std::this_thread::sleep_for(std::chrono::milliseconds(100));
366
367 auto live_sensor = secondary.server.MakeStream();
368 EXPECT_FALSE(secondary.registry.Bind(secondary.server, 7u, StreamIdOf(live_sensor), false).has_value());
369 EXPECT_TRUE(WriteUntilReceived(live_sensor, received, 500));
370 client.UnSubscribe(*token);
371}
372
373TEST(MultiGpuSensorStreams, bind_refuses_a_stream_already_bound_to_another_actor) {
374 SecondaryStreams secondary;
375 auto sensor = secondary.server.MakeStream();
376 EXPECT_FALSE(secondary.registry.Bind(secondary.server, 10u, StreamIdOf(sensor), true).has_value());
377 ASSERT_TRUE(secondary.registry.Resolve(secondary.server, 10u, 101u).has_value());
378
379 EXPECT_FALSE(secondary.registry.Bind(secondary.server, 20u, StreamIdOf(sensor), false).has_value());
380 ASSERT_TRUE(secondary.registry.Resolve(secondary.server, 20u, 102u).has_value());
381 EXPECT_EQ(secondary.server.FindStreamAlias(101u), std::optional<stream_id_type>(StreamIdOf(sensor)));
382 const auto other = secondary.server.FindStreamAlias(102u);
383 ASSERT_TRUE(other.has_value());
384 EXPECT_NE(*other, StreamIdOf(sensor));
385}
386
387TEST(streaming, closing_a_session_after_its_alias_moved_leaves_the_new_stream_session_alone) {
389 server.SetSessionsUseStreamAliases(true);
390 server.AsyncRun(1u);
391 auto old_stream = server.MakeStream();
392 auto new_stream = server.MakeStream();
393 const auto port = token_type(old_stream.token()).get_port();
394
395 // Raw sockets, unlike carla::streaming::Client, never reconnect, which
396 // would hide a session wrongly dropped by the server.
397 boost::asio::io_context io_context;
398 server.SetStreamAlias(100u, StreamIdOf(old_stream));
399 auto old_session = OpenRawSession(io_context, port, 100u);
400 ASSERT_TRUE(WriteUntilReadable(old_stream, old_session));
401
402 server.SetStreamAlias(100u, StreamIdOf(new_stream));
403 auto new_session = OpenRawSession(io_context, port, 100u);
404 ASSERT_TRUE(WriteUntilReadable(new_stream, new_session));
405
406 // The server notices the closed session when writing to it.
407 old_session.close();
408 for (int i = 0; i < 20; ++i) {
409 WriteMessage(old_stream, "data");
410 std::this_thread::sleep_for(std::chrono::milliseconds(10));
411 }
412 Drain(new_session);
413 EXPECT_TRUE(WriteUntilReadable(new_stream, new_session));
414}
415
416namespace {
417
419
420 /// A secondary's actor registry and the replay of add/remove events onto
421 /// it, as FFrameData::PlayFrameData does.
422 struct FakeSecondary {
423 struct Actor {
424 std::string blueprint;
425 std::string mesh;
426 bool map_registered = false;
427 bool dormant = false;
428 };
429
430 struct Lookup {
431 const FakeSecondary &secondary;
432 std::string blueprint;
433 std::string mesh;
434
435 bool IsSameKind(uint32_t id) const {
436 const auto it = secondary.actors.find(id);
437 return (it != secondary.actors.end()) && (it->second.blueprint == blueprint);
438 }
439
440 std::optional<uint32_t> FindMapActor() const {
441 if (blueprint == "spectator") {
442 return secondary.spectator;
443 }
444 for (const auto &[id, actor] : secondary.actors) {
445 if (IsMapActorLike(id) && (secondary.place.count(id) > 0u) && (secondary.place.at(id) == mesh + "@place")) {
446 return id;
447 }
448 }
449 return std::nullopt;
450 }
451
452 bool IsMapActorLike(uint32_t id) const {
453 const auto it = secondary.actors.find(id);
454 return (it != secondary.actors.end()) && it->second.map_registered &&
455 (it->second.blueprint == blueprint) && (it->second.mesh == mesh);
456 }
457
458 bool Contains(uint32_t id) const {
459 return secondary.actors.count(id) > 0u;
460 }
461 };
462
463 std::map<uint32_t, Actor> actors;
464 std::map<uint32_t, std::string> place;
465 std::optional<uint32_t> spectator;
466 MirroredActorMap mirrors;
467 uint32_t counter = 0u;
468
469 uint32_t AddMapActor(std::string blueprint, std::string mesh = {}) {
470 const uint32_t id = ++counter;
471 actors[id] = Actor{std::move(blueprint), std::move(mesh), true, false};
472 return id;
473 }
474
475 carla::multigpu::ReplayedActorPlan Plan(uint32_t primary_id, const std::string &blueprint, const std::string &mesh = {}) const {
476 return carla::multigpu::PlanReplayedActor(mirrors, primary_id, Lookup{*this, blueprint, mesh});
477 }
478
479 /// Mirrors FActorRegistry::Register: a requested id that is taken by a
480 /// dormant actor would be attached to it, so the plan must never ask for one.
481 uint32_t Replay(uint32_t primary_id, const std::string &blueprint, const std::string &mesh = {}) {
482 const auto plan = Plan(primary_id, blueprint, mesh);
483 uint32_t local_id = 0u;
484 if (plan.reuse) {
485 local_id = *plan.reuse;
486 } else {
487 EXPECT_EQ(actors.count(plan.spawn_id), 0u) << "spawn requested taken id " << plan.spawn_id;
488 local_id = ++counter;
489 if ((plan.spawn_id != 0u) && (actors.count(plan.spawn_id) == 0u)) {
490 local_id = plan.spawn_id;
491 counter = std::max(counter, local_id);
492 }
493 actors[local_id] = Actor{blueprint, mesh, false, false};
494 }
495 mirrors[primary_id] = local_id;
496 return local_id;
497 }
498
499 void Remove(uint32_t primary_id) {
500 actors.erase(mirrors[primary_id]);
501 mirrors.erase(primary_id);
502 }
503 };
504
505 bool MirrorsAreDistinct(const MirroredActorMap &mirrors) {
506 std::set<uint32_t> locals;
507 for (const auto &item : mirrors) {
508 if (!locals.insert(item.second).second) {
509 return false;
510 }
511 }
512 return true;
513 }
514
515} // namespace
516
517TEST(MultiGpuMirroredActors, a_local_id_equal_to_the_primary_id_of_another_mirror_is_not_reused) {
518 FakeSecondary secondary;
519 secondary.counter = 156u;
520 secondary.actors[157u] = {"sensor.camera.rgb", {}, false, false};
521 secondary.mirrors[153u] = 157u;
522
523 const auto plan = secondary.Plan(157u, "sensor.camera.rgb");
524 EXPECT_FALSE(plan.reuse.has_value());
525 EXPECT_EQ(plan.spawn_id, 0u);
526}
527
528TEST(MultiGpuMirroredActors, restarted_secondary_gives_every_primary_camera_its_own_actor) {
529 // The log of the restarted secondary: its counter ran 4 ids ahead, so its
530 // own map actors hold the primary's ids 151 to 156.
531 FakeSecondary secondary;
532 secondary.counter = 146u;
533 for (int i = 0; i < 10; ++i) {
534 secondary.AddMapActor("traffic.stop");
535 }
536 for (uint32_t primary_id = 153u; primary_id <= 160u; ++primary_id) {
537 secondary.Replay(primary_id, "sensor.camera.rgb");
538 }
539 EXPECT_EQ(secondary.mirrors.size(), 8u);
540 EXPECT_TRUE(MirrorsAreDistinct(secondary.mirrors));
541 EXPECT_EQ(secondary.actors.size(), 18u);
542}
543
544TEST(MultiGpuMirroredActors, an_id_held_by_a_dormant_actor_is_never_requested) {
545 FakeSecondary secondary;
546 secondary.counter = 160u;
547 secondary.actors[157u] = {"vehicle.lincoln.mkz", {}, false, true};
548 secondary.mirrors[153u] = 157u;
549
550 const auto plan = secondary.Plan(157u, "sensor.camera.rgb");
551 EXPECT_FALSE(plan.reuse.has_value());
552 EXPECT_EQ(plan.spawn_id, 0u);
553}
554
555TEST(MultiGpuMirroredActors, a_free_id_is_requested_for_a_new_actor) {
556 FakeSecondary secondary;
557 const auto plan = secondary.Plan(42u, "vehicle.lincoln.mkz");
558 EXPECT_FALSE(plan.reuse.has_value());
559 EXPECT_EQ(plan.spawn_id, 42u);
560}
561
562TEST(MultiGpuMirroredActors, a_mapped_actor_reuses_its_mirror_even_if_its_id_is_taken) {
563 FakeSecondary secondary;
564 secondary.counter = 160u;
565 secondary.actors[153u] = {"traffic.stop", {}, true, false};
566 secondary.actors[157u] = {"sensor.camera.rgb", {}, false, false};
567 secondary.mirrors[153u] = 157u;
568
569 EXPECT_EQ(secondary.Plan(153u, "sensor.camera.rgb").reuse, std::optional<uint32_t>(157u));
570 EXPECT_EQ(secondary.Replay(153u, "sensor.camera.rgb"), 157u);
571 EXPECT_EQ(secondary.actors.size(), 2u);
572}
573
574TEST(MultiGpuMirroredActors, a_map_prop_at_the_primary_id_is_reused_and_mapped) {
575 FakeSecondary secondary;
576 const uint32_t prop = secondary.AddMapActor("static.prop.mesh", "cone");
577
578 EXPECT_EQ(secondary.Replay(prop, "static.prop.mesh", "cone"), prop);
579 EXPECT_EQ(secondary.mirrors.at(prop), prop);
580 EXPECT_EQ(secondary.actors.size(), 1u);
581 EXPECT_EQ(secondary.Plan(prop, "static.prop.mesh", "cone").reuse, std::optional<uint32_t>(prop));
582}
583
584TEST(MultiGpuMirroredActors, a_map_prop_at_its_place_is_reused_whatever_its_id) {
585 FakeSecondary secondary;
586 secondary.AddMapActor("static.prop.mesh", "barrel");
587 const uint32_t cone = secondary.AddMapActor("static.prop.mesh", "cone");
588 secondary.place[cone] = "cone@place";
589
590 EXPECT_EQ(secondary.Replay(90u, "static.prop.mesh", "cone"), cone);
591 EXPECT_EQ(secondary.actors.size(), 2u);
592}
593
594TEST(MultiGpuMirroredActors, a_map_prop_mirroring_another_primary_actor_is_not_reused) {
595 FakeSecondary secondary;
596 const uint32_t prop = secondary.AddMapActor("static.prop.mesh", "cone");
597 secondary.mirrors[12u] = prop;
598
599 const auto plan = secondary.Plan(prop, "static.prop.mesh", "cone");
600 EXPECT_FALSE(plan.reuse.has_value());
601 EXPECT_EQ(plan.spawn_id, 0u);
602}
603
604TEST(MultiGpuMirroredActors, a_map_prop_with_another_mesh_is_not_reused) {
605 FakeSecondary secondary;
606 const uint32_t prop = secondary.AddMapActor("static.prop.mesh", "barrel");
607 EXPECT_FALSE(secondary.Plan(prop, "static.prop.mesh", "cone").reuse.has_value());
608}
609
610TEST(MultiGpuMirroredActors, the_spectator_is_reused_whatever_its_id) {
611 FakeSecondary secondary;
612 secondary.spectator = secondary.AddMapActor("spectator");
613
614 EXPECT_EQ(secondary.Replay(7u, "spectator"), *secondary.spectator);
615 EXPECT_EQ(secondary.actors.size(), 1u);
616}
617
618TEST(MultiGpuMirroredActors, a_removed_actor_is_spawned_again_on_its_next_add) {
619 FakeSecondary secondary;
620 const uint32_t first = secondary.Replay(30u, "vehicle.lincoln.mkz");
621 secondary.Remove(30u);
622 EXPECT_EQ(secondary.mirrors.count(30u), 0u);
623
624 secondary.AddMapActor("traffic.stop");
625 const uint32_t second = secondary.Replay(30u, "vehicle.lincoln.mkz");
626 EXPECT_EQ(secondary.actors.count(second), 1u);
627 EXPECT_EQ(secondary.mirrors.at(30u), second);
628 EXPECT_EQ(first, 30u);
629}
630
631TEST(MultiGpuMirroredActors, a_stale_mirror_of_another_kind_is_not_reused) {
632 FakeSecondary secondary;
633 secondary.counter = 50u;
634 secondary.actors[50u] = {"vehicle.lincoln.mkz", {}, false, false};
635 secondary.mirrors[30u] = 50u;
636
637 const auto plan = secondary.Plan(30u, "sensor.camera.rgb");
638 EXPECT_FALSE(plan.reuse.has_value());
639 EXPECT_EQ(plan.spawn_id, 30u);
640}
641
642TEST(MultiGpuSensorStreams, restarted_secondary_binds_overlapping_ids_to_their_own_streams) {
643 SecondaryStreams streams;
644 FakeSecondary secondary;
645 secondary.counter = 146u;
646 for (int i = 0; i < 10; ++i) {
647 secondary.AddMapActor("traffic.stop");
648 }
649 std::map<uint32_t, carla::streaming::Stream> sensors;
650 for (uint32_t primary_id = 153u; primary_id <= 160u; ++primary_id) {
651 const uint32_t local_id = secondary.Replay(primary_id, "sensor.camera.rgb");
652 auto it = sensors.find(local_id);
653 if (it == sensors.end()) {
654 it = sensors.emplace(local_id, streams.server.MakeStream()).first;
655 }
656 EXPECT_FALSE(streams.registry.Bind(streams.server, primary_id, StreamIdOf(it->second), true).has_value());
657 }
658 ASSERT_EQ(sensors.size(), 8u);
659
660 for (uint32_t primary_id = 153u; primary_id <= 160u; ++primary_id) {
661 ASSERT_TRUE(streams.registry.Resolve(streams.server, primary_id, 1000u + primary_id).has_value());
662 EXPECT_EQ(
663 streams.server.FindStreamAlias(1000u + primary_id),
664 std::optional<stream_id_type>(StreamIdOf(sensors.at(secondary.mirrors.at(primary_id)))));
665 }
666}
667
668TEST(MultiGpuSensorStreams, self_aliased_broadcast_stream_is_reachable_with_aliases_on) {
669 // A secondary's own world snapshots (stream 1 of its server), aliased to
670 // itself so that a direct client of the secondary can read them.
671 SecondaryStreams secondary;
672 auto broadcast = secondary.server.MakeStream();
673 const stream_id_type broadcast_id = StreamIdOf(broadcast);
674 secondary.server.SetStreamAlias(broadcast_id, broadcast_id);
675
676 std::atomic_bool received{false};
678 client.AsyncRun(1u);
679 const auto token = secondary.server.FindToken(broadcast_id);
680 ASSERT_TRUE(token.has_value());
681 client.Subscribe(*token, [&received](auto) { received = true; });
682 EXPECT_TRUE(WriteUntilReceived(broadcast, received));
683 client.UnSubscribe(*token);
684}
685
686TEST(MultiGpuSensorStreams, episode_change_keeps_the_broadcast_alias) {
687 SecondaryStreams secondary;
688 auto broadcast = secondary.server.MakeStream();
689 const stream_id_type broadcast_id = StreamIdOf(broadcast);
690 secondary.server.SetStreamAlias(broadcast_id, broadcast_id);
691 auto sensor = secondary.server.MakeStream();
692 EXPECT_FALSE(secondary.registry.Bind(secondary.server, 7u, StreamIdOf(sensor), true).has_value());
693 ASSERT_TRUE(secondary.registry.Resolve(secondary.server, 7u, 100u).has_value());
694
695 secondary.registry.BeginEpisodeChange(secondary.server);
696 EXPECT_FALSE(secondary.server.FindStreamAlias(100u).has_value());
697 EXPECT_EQ(secondary.server.FindStreamAlias(broadcast_id), std::optional<stream_id_type>(broadcast_id));
698}
699
long long first
Definition LaneGraph.cpp:34
static std::shared_ptr< BufferView > CreateFrom(Buffer &&buffer)
Definition BufferView.h:56
A piece of raw data.
Secondary-side map from a primary sensor actor to its local stream, exposed to clients only through a...
A client able to subscribe to multiple streams.
void AsyncRun(size_t worker_threads)
void Subscribe(const Token &token, Functor &&callback)
void UnSubscribe(const Token &token)
A streaming server.
std::optional< stream_id > FindStreamAlias(stream_id alias)
void RemoveStreamAlias(stream_id alias)
bool IsEnabledForROS(stream_id sensor_id)
std::optional< token_type > FindToken(stream_id id)
Token of an existing stream, or std::nullopt if id is unknown.
void SetStreamAlias(stream_id alias, stream_id target)
void AsyncRun(size_t worker_threads)
void SetSessionsUseStreamAliases(bool enabled)
Token token() const
Token associated with this stream.
void Write(Buffers &&... buffers)
Flush buffers down the stream. No copies are made.
Serializes a stream endpoint.
ReplayedActorPlan PlanReplayedActor(const MirroredActorMap &mirrors, uint32_t primary_id, const LocalActors &local)
Where a secondary replays the add event of primary actor primary_id.
carla::streaming::detail::token_type token_type
std::unordered_map< uint32_t, uint32_t > MirroredActorMap
Primary actor id -> id of the local actor a secondary replayed it onto, for the current episode only.
carla::SharedPtr< cc::Actor > Actor
constexpr uint16_t TESTING_PORT
Definition test.h:24
TEST(streaming, find_token_does_not_create_a_stream_for_an_unknown_id)