diff --git a/src/proto/message_translation/ssl_referee.cpp b/src/proto/message_translation/ssl_referee.cpp index a6bdf36f35..8015853daf 100644 --- a/src/proto/message_translation/ssl_referee.cpp +++ b/src/proto/message_translation/ssl_referee.cpp @@ -2,6 +2,14 @@ #include "shared/constants.h" +// this set contains an ongoing list of deprecated SSLProto::Referee_Commands +const static std::unordered_set deprecated_commands = { + SSLProto::Referee_Command_GOAL_YELLOW, + SSLProto::Referee_Command_GOAL_BLUE, + SSLProto::Referee_Command_INDIRECT_FREE_YELLOW, + SSLProto::Referee_Command_INDIRECT_FREE_BLUE, +}; + // this maps a protobuf SSLProto::Referee_Command enum to its equivalent internal type // this map is used when we are on the blue team const static std::unordered_map @@ -62,6 +70,8 @@ const static std::unordered_map RefereeCommand createRefereeCommand(const SSLProto::Referee &packet, TeamColour team_colour) { + if (deprecated_commands.contains(packet.command())) + return RefereeCommand::HALT; if (team_colour == TeamColour::YELLOW) { return yellow_team_command_map.at(packet.command()); diff --git a/src/proto/message_translation/ssl_referee_test.cpp b/src/proto/message_translation/ssl_referee_test.cpp index 7f64a06061..34eef0e2d1 100644 --- a/src/proto/message_translation/ssl_referee_test.cpp +++ b/src/proto/message_translation/ssl_referee_test.cpp @@ -124,17 +124,6 @@ INSTANTIATE_TEST_CASE_P( std::make_tuple(RefereeCommand::DIRECT_FREE_THEM, SSLProto::Referee_Command_DIRECT_FREE_YELLOW, TeamColour::BLUE), - // indirect free - std::make_tuple(RefereeCommand::INDIRECT_FREE_US, - SSLProto::Referee_Command_INDIRECT_FREE_YELLOW, - TeamColour::YELLOW), - std::make_tuple(RefereeCommand::INDIRECT_FREE_US, - SSLProto::Referee_Command_INDIRECT_FREE_BLUE, TeamColour::BLUE), - std::make_tuple(RefereeCommand::INDIRECT_FREE_THEM, - SSLProto::Referee_Command_INDIRECT_FREE_BLUE, TeamColour::YELLOW), - std::make_tuple(RefereeCommand::INDIRECT_FREE_THEM, - SSLProto::Referee_Command_INDIRECT_FREE_YELLOW, TeamColour::BLUE), - // timeout std::make_tuple(RefereeCommand::TIMEOUT_US, SSLProto::Referee_Command_TIMEOUT_YELLOW, TeamColour::YELLOW), diff --git a/src/software/sensor_fusion/sensor_fusion_test.cpp b/src/software/sensor_fusion/sensor_fusion_test.cpp index 3cb5dffdfb..aa70021016 100644 --- a/src/software/sensor_fusion/sensor_fusion_test.cpp +++ b/src/software/sensor_fusion/sensor_fusion_test.cpp @@ -25,8 +25,8 @@ class SensorFusionTest : public ::testing::Test robot_status_msg_dribble_motor_hot(initDribbleMotorHotErrorCode()), robot_status_msg_multiple_error_codes(initMultipleErrorCode()), robot_status_msg_no_error_code(initNoErrorCode()), - referee_indirect_yellow(initRefereeIndirectYellow()), - referee_indirect_blue(initRefereeIndirectBlue()), + referee_direct_yellow(initRefereeDirectYellow()), + referee_direct_blue(initRefereeDirectBlue()), referee_normal_start(initRefereeNormalStart()), referee_ball_placement_yellow(initRefereeBallPlacementYellow()), referee_ball_placement_blue(initRefereeBallPlacementBlue()), @@ -49,8 +49,8 @@ class SensorFusionTest : public ::testing::Test std::unique_ptr robot_status_msg_dribble_motor_hot; std::unique_ptr robot_status_msg_multiple_error_codes; std::unique_ptr robot_status_msg_no_error_code; - std::unique_ptr referee_indirect_yellow; - std::unique_ptr referee_indirect_blue; + std::unique_ptr referee_direct_yellow; + std::unique_ptr referee_direct_blue; std::unique_ptr referee_normal_start; std::unique_ptr referee_ball_placement_yellow; std::unique_ptr referee_ball_placement_blue; @@ -282,17 +282,17 @@ class SensorFusionTest : public ::testing::Test return robot_msg; } - std::unique_ptr initRefereeIndirectYellow() + std::unique_ptr initRefereeDirectYellow() { auto ref_msg = std::make_unique(); - ref_msg->set_command(SSLProto::Referee_Command_INDIRECT_FREE_YELLOW); + ref_msg->set_command(SSLProto::Referee_Command_DIRECT_FREE_YELLOW); return ref_msg; } - std::unique_ptr initRefereeIndirectBlue() + std::unique_ptr initRefereeDirectBlue() { auto ref_msg = std::make_unique(); - ref_msg->set_command(SSLProto::Referee_Command_INDIRECT_FREE_BLUE); + ref_msg->set_command(SSLProto::Referee_Command_DIRECT_FREE_BLUE); return ref_msg; } @@ -545,7 +545,7 @@ TEST_F(SensorFusionTest, test_complete_wrapper_with_robot_status_msg_2_at_a_time TEST_F(SensorFusionTest, test_referee_yellow_then_normal) { GameState expected_1; - expected_1.updateRefereeCommand(RefereeCommand::INDIRECT_FREE_US); + expected_1.updateRefereeCommand(RefereeCommand::DIRECT_FREE_US); GameState expected_2 = expected_1; expected_2.updateRefereeCommand(RefereeCommand::NORMAL_START); @@ -555,7 +555,7 @@ TEST_F(SensorFusionTest, test_referee_yellow_then_normal) createSSLWrapperPacket(std::move(geom_data), initDetectionFrame()); // set vision msg so that world is valid *(sensor_msg_1.mutable_ssl_vision_msg()) = *ssl_wrapper_packet; - *(sensor_msg_1.mutable_ssl_referee_msg()) = *referee_indirect_yellow; + *(sensor_msg_1.mutable_ssl_referee_msg()) = *referee_direct_yellow; sensor_fusion.processSensorProto(sensor_msg_1); World result_1 = *sensor_fusion.getWorld(); EXPECT_EQ(expected_1, result_1.gameState()); @@ -570,7 +570,7 @@ TEST_F(SensorFusionTest, test_referee_yellow_then_normal) TEST_F(SensorFusionTest, test_referee_blue_then_normal) { GameState expected_1; - expected_1.updateRefereeCommand(RefereeCommand::INDIRECT_FREE_THEM); + expected_1.updateRefereeCommand(RefereeCommand::DIRECT_FREE_THEM); GameState expected_2 = expected_1; expected_2.updateRefereeCommand(RefereeCommand::NORMAL_START); @@ -580,7 +580,7 @@ TEST_F(SensorFusionTest, test_referee_blue_then_normal) createSSLWrapperPacket(std::move(geom_data), initDetectionFrame()); // set vision msg so that world is valid *(sensor_msg_1.mutable_ssl_vision_msg()) = *ssl_wrapper_packet; - *(sensor_msg_1.mutable_ssl_referee_msg()) = *referee_indirect_blue; + *(sensor_msg_1.mutable_ssl_referee_msg()) = *referee_direct_blue; sensor_fusion.processSensorProto(sensor_msg_1); World result_1 = *sensor_fusion.getWorld(); EXPECT_EQ(expected_1, result_1.gameState()); diff --git a/src/software/thunderscope/binary_context_managers/BUILD b/src/software/thunderscope/binary_context_managers/BUILD index 8396ae6351..835615fcfd 100644 --- a/src/software/thunderscope/binary_context_managers/BUILD +++ b/src/software/thunderscope/binary_context_managers/BUILD @@ -22,6 +22,7 @@ py_library( deps = [ "//proto:import_all_protos", "//software/networking:ssl_proto_communication", + "//software/thunderscope/common:thread_safe_circular_buffer", ], ) diff --git a/src/software/thunderscope/binary_context_managers/game_controller.py b/src/software/thunderscope/binary_context_managers/game_controller.py index d43bbece79..ca89d88f89 100644 --- a/src/software/thunderscope/binary_context_managers/game_controller.py +++ b/src/software/thunderscope/binary_context_managers/game_controller.py @@ -1,5 +1,6 @@ from __future__ import annotations +import queue import random import logging import os @@ -17,8 +18,12 @@ from software.py_constants import * from software.thunderscope.binary_context_managers.util import * from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer +from software.thunderscope.common.thread_safe_circular_buffer import ( + ThreadSafeCircularBuffer, +) logger = logging.getLogger(__name__) +import itertools class Gamecontroller: @@ -29,7 +34,10 @@ class Gamecontroller: CI_MODE_OUTPUT_RECEIVE_BUFFER_SIZE = 9000 def __init__( - self, suppress_logs: bool = False, use_conventional_port: bool = False + self, + suppress_logs: bool = False, + use_conventional_port: bool = False, + simulator_proto_unix_io: ProtoUnixIO = None, ) -> None: """Run Gamecontroller @@ -54,6 +62,14 @@ def __init__( buffer_size=2, protobuf_type=ManualGCCommand ) + self.simulator_proto_unix_io = simulator_proto_unix_io + self.blue_team_world_buffer = ThreadSafeCircularBuffer( + buffer_size=1, protobuf_type=World + ) + self.latest_world = None + self.blue_removed_robot_ids = queue.Queue() + self.yellow_removed_robot_ids = queue.Queue() + def get_referee_port(self) -> int: """Sometimes, the port that we are using changes depending on context. We want a getter function that returns the port we are using. @@ -95,7 +111,6 @@ def __exit__(self, type, value, traceback) -> None: """ self.gamecontroller_proc.terminate() self.gamecontroller_proc.wait() - self.ci_socket.close() def refresh(self): @@ -119,6 +134,104 @@ def refresh(self): ) manual_command = self.command_override_buffer.get(return_cached=False) + @staticmethod + def __update_robot_count( + robot_states, + team: Team, + max_robots: int, + removed_robot_ids: queue.Queue[int], + field_edge_y_meters: float, + ) -> None: + """Static method for updating the number of robots in the world state (i.e. robot_states) for a given Team. + This method is team & side agnostic. + + :param robot_states: WorldState map protobuf to be updated. + :param team: the Team of robots currently in play + :param max_robots: The number of robots we should have on the field. Must be >= 0 + :param removed_robot_ids: The robots (IDs) which have been removed already and can safely be re-added + :param field_edge_y_meters: Places new robots at this y position along the centerline + :return: + """ + # build the robot state for placing robots at the edge of field + place_state = RobotState( + global_position=Point(x_meters=0, y_meters=field_edge_y_meters) + ) + # Remove robots, as we have too many. Set robot velocities to zero to avoid any drift + for count, robot in enumerate(team.team_robots, start=1): + if count <= max_robots: + robot_states[robot.id].CopyFrom(robot.current_state) + velocity = robot_states[robot.id].global_velocity + velocity.x_component_meters = 0 + velocity.y_component_meters = 0 + else: + removed_robot_ids.put(robot.id) + # Add robots, since we are missing some + robots_diff: int = max_robots - len(team.team_robots) + if robots_diff <= 0: + return + for _ in range(robots_diff): + try: + robot_states[removed_robot_ids.get_nowait()].CopyFrom(place_state) + except queue.Empty: + return + + def handle_referee(self, referee: Referee) -> None: + """Updates the world state based on the referee message + :param referee: the referee protobuf message + """ + # Check that we are running with the simulator and have access to its + # proto unix io + if self.simulator_proto_unix_io is None: + return + + # Convert the latest blue world into a WorldState we can send to the simulator and update the robots + self.latest_world = self.blue_team_world_buffer.get( + block=False, return_cached=True + ) + + max_allowed_bots_yellow: int = referee.yellow.max_allowed_bots + max_allowed_bots_blue: int = referee.blue.max_allowed_bots + # Ignore if nothing needs to be updated + if ( + len(self.latest_world.friendly_team.team_robots) == max_allowed_bots_blue + and len(self.latest_world.enemy_team.team_robots) == max_allowed_bots_yellow + ): + return + + # Populate a blank WorldState with new updated robot information + world_state = WorldState() + field_edge_y_meters: Final[int] = ( + self.latest_world.field.field_y_length + - self.latest_world.field.boundary_buffer_size + ) + + self.__update_robot_count( + world_state.blue_robots, + self.latest_world.friendly_team, + max_allowed_bots_blue, + self.blue_removed_robot_ids, + field_edge_y_meters, + ) + self.__update_robot_count( + world_state.yellow_robots, + self.latest_world.enemy_team, + max_allowed_bots_yellow, + self.yellow_removed_robot_ids, + -field_edge_y_meters, + ) + + # Check if we need to invert the world state + if referee.blue_team_on_positive_half: + for robot in itertools.chain( + world_state.blue_robots, world_state.yellow_robots + ): + robot.current_state.global_position.x_meters *= -1 + robot.current_state.global_position.y_meters *= -1 + robot.current_state.global_orientation.radians += math.pi + + # Send out updated world state + self.simulator_proto_unix_io.send_proto(WorldState, world_state) + def is_valid_port(self, port): """Determine whether or not a given port is valid @@ -168,6 +281,7 @@ def __send_referee_command(data: Referee) -> None: :param data: The referee command to send """ + self.handle_referee(data) blue_full_system_proto_unix_io.send_proto(Referee, data) yellow_full_system_proto_unix_io.send_proto(Referee, data) if autoref_proto_unix_io is not None: @@ -187,6 +301,9 @@ def __send_referee_command(data: Referee) -> None: yellow_full_system_proto_unix_io.register_observer( ManualGCCommand, self.command_override_buffer ) + blue_full_system_proto_unix_io.register_observer( + World, self.blue_team_world_buffer + ) def send_gc_command( self, diff --git a/src/software/thunderscope/common/BUILD b/src/software/thunderscope/common/BUILD index 704af35961..28e6bf83e7 100644 --- a/src/software/thunderscope/common/BUILD +++ b/src/software/thunderscope/common/BUILD @@ -52,3 +52,11 @@ py_library( requirement("pyqt-toast-notification"), ], ) + +py_library( + name = "thread_safe_circular_buffer", + srcs = ["thread_safe_circular_buffer.py"], + deps = [ + "//software/thunderscope:thread_safe_buffer", + ], +) diff --git a/src/software/thunderscope/common/proto_plotter.py b/src/software/thunderscope/common/proto_plotter.py index 81cf5fa0a1..c2ed21b256 100644 --- a/src/software/thunderscope/common/proto_plotter.py +++ b/src/software/thunderscope/common/proto_plotter.py @@ -100,7 +100,7 @@ def refresh(self) -> None: # Dump the entire buffer into a deque. This operation is fast because # its just consuming data from the buffer and appending it to a deque. for proto_class, buffer in self.buffers.items(): - for _ in range(buffer.queue.qsize()): + for _ in range(buffer.size()): data = self.configuration[proto_class](buffer.get(block=False)) # If named_value is new, create a plot and for the new value and diff --git a/src/software/thunderscope/common/thread_safe_circular_buffer.py b/src/software/thunderscope/common/thread_safe_circular_buffer.py new file mode 100644 index 0000000000..a977081eff --- /dev/null +++ b/src/software/thunderscope/common/thread_safe_circular_buffer.py @@ -0,0 +1,87 @@ +from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer +from collections import deque +from typing import Type, Optional +from google.protobuf.message import Message +from typing import override + + +class ThreadSafeCircularBuffer(ThreadSafeBuffer): + """Multiple producer, multiple consumer buffer. See: ThreadSafeBuffer + + │ buffer_size │ + ├──────────────────────────────────────────┤ + │ │ + + ┌──────┬──────┬──────┬──────┬──────┬───────┐ + put() │ │ │ │ │ │ │ get() + └──────┴──────┴──────┴──────┴──────┴───────┘ + ThreadSafeCircularBuffer + """ + + def __init__( + self, buffer_size: int, protobuf_type: Type[Message], log_overrun: bool = False + ) -> None: + """A circular buffer to hold data to be consumed. + + :param buffer_size: The max size of the buffer. + :param protobuf_type: To buffer + :param log_overrun: If True, warns when we lose protos during future operations + """ + super().__init__(buffer_size, protobuf_type) + self.log_overrun = log_overrun + self.buffer = deque(maxlen=buffer_size) + self.empty_exception = IndexError + + @override + def get( + self, block: bool = False, timeout: float = None, return_cached: bool = True + ) -> Optional[Message]: + """Get data from the buffer immediately. + + If the buffer is empty: + - Return cached message if return_cached is True, otherwise returns None + + :param block: This does nothing as all operations are immediate + :param timeout: This does nothing as all operations are immediate + :param return_cached: If buffer is empty, decides whether to + return cached value (True) or return None (false) + :return: protobuf (cached if there is no data in the buffer and return_cached is True) + """ + if ( + self.log_overrun + and self.protos_dropped > self.last_logged_protos_dropped + and self.protos_dropped > self.MIN_DROPPED_BEFORE_LOG + ): + self.logger.warn( + "packets dropped; thunderscope did not show {} protos".format( + self.protos_dropped + ) + ) + self.last_logged_protos_dropped = self.protos_dropped + + try: + self.cached_msg = self.buffer.popleft() + except self.empty_exception: + if not return_cached: + return None + + return self.cached_msg + + @override + def put(self, proto: Message, block: bool = False, timeout: float = None) -> None: + """Put data into the buffer. If the buffer is full, the proto may be logged. + + :param proto: The proto to place in the buffer + :param block: True blocks overwriting items in a full buffer, dropping the proto. False writes every time + :param timeout: This does nothing as all operations are immediate + """ + if len(self.buffer) == self.buffer.maxlen: + self.protos_dropped += 1 + if block: + return + self.buffer.append(proto) + + @override + def size(self) -> int: + """Returns the number of objects in the buffer""" + return len(self.buffer) diff --git a/src/software/thunderscope/gl/layers/gl_cost_vis_layer.py b/src/software/thunderscope/gl/layers/gl_cost_vis_layer.py index 9a0c06b2e7..633361e228 100644 --- a/src/software/thunderscope/gl/layers/gl_cost_vis_layer.py +++ b/src/software/thunderscope/gl/layers/gl_cost_vis_layer.py @@ -4,7 +4,6 @@ import pyqtgraph as pg import time -import queue import numpy as np from proto.world_pb2 import World @@ -101,11 +100,7 @@ def refresh_graphics(self) -> None: """Update graphics in this layer""" self.cached_world = self.world_buffer.get(block=False) field = self.cached_world.field - - try: - cost_vis = self.cost_visualization_buffer.queue.get_nowait() - except queue.Empty: - cost_vis = None + cost_vis = self.cost_visualization_buffer.get(block=False, return_cached=False) self.cost_vis_overlay_layer.refresh_graphics() diff --git a/src/software/thunderscope/gl/layers/gl_passing_layer.py b/src/software/thunderscope/gl/layers/gl_passing_layer.py index a8966f781b..0c40c57d18 100644 --- a/src/software/thunderscope/gl/layers/gl_passing_layer.py +++ b/src/software/thunderscope/gl/layers/gl_passing_layer.py @@ -1,7 +1,6 @@ from pyqtgraph.opengl import * import time -import queue from proto.visualization_pb2 import PassVisualization @@ -41,10 +40,7 @@ def __init__(self, name: str, buffer_size: int = 5) -> None: def refresh_graphics(self) -> None: """Update graphics in this layer""" - try: - pass_vis = self.pass_visualization_buffer.queue.get_nowait() - except queue.Empty: - pass_vis = None + pass_vis = self.pass_visualization_buffer.get(block=False, return_cached=False) if not pass_vis: pass_vis = self.cached_pass_vis diff --git a/src/software/thunderscope/gl/layers/gl_tactic_layer.py b/src/software/thunderscope/gl/layers/gl_tactic_layer.py index a7b5f3c671..a006ea0d7e 100644 --- a/src/software/thunderscope/gl/layers/gl_tactic_layer.py +++ b/src/software/thunderscope/gl/layers/gl_tactic_layer.py @@ -40,7 +40,7 @@ def __init__(self, name: str, buffer_size: int = 5) -> None: def refresh_graphics(self) -> None: """Update graphics in this layer""" self.cached_world = self.world_buffer.get(block=False) - play_info = self.play_info_buffer.get(block=False).bounds() + play_info = self.play_info_buffer.get(block=False) self.__update_tactic_name_graphics(self.cached_world.friendly_team, play_info) @@ -50,7 +50,7 @@ def __update_tactic_name_graphics(self, team: Team, play_info) -> None: :param team: The team proto :param play_info: The dictionary containing play/tactic info """ - tactic_assignments = play_info.robot_tactic_assignment() + tactic_assignments = play_info.robot_tactic_assignment # Ensure we have the same number of graphics as robots self.tactic_fsm_info_graphics.resize( @@ -68,8 +68,8 @@ def __update_tactic_name_graphics(self, team: Team, play_info) -> None: tactic_fsm_info_graphic.setData( text=textwrap.dedent( f""" - {tactic_assignments[str(robot.id)]["tacticName"]} - - {tactic_assignments[str(robot.id)]["tacticFsmState"]} + {tactic_assignments[robot.id].tactic_name} - + {tactic_assignments[robot.id].tactic_fsm_state} """ ), pos=[ diff --git a/src/software/thunderscope/gl/layers/gl_validation_layer.py b/src/software/thunderscope/gl/layers/gl_validation_layer.py index 41194dedbf..a1d1f571a4 100644 --- a/src/software/thunderscope/gl/layers/gl_validation_layer.py +++ b/src/software/thunderscope/gl/layers/gl_validation_layer.py @@ -101,7 +101,7 @@ def __init__( def refresh_graphics(self) -> None: """Update graphics in this layer""" # Consume the validation set buffer - for _ in range(self.validation_set_buffer.queue.qsize()): + for _ in range(self.validation_set_buffer.size()): self.validation_set = self.validation_set_buffer.get() if self.validation_set.validation_type == ValidationType.ALWAYS: diff --git a/src/software/thunderscope/log/g3log_widget.py b/src/software/thunderscope/log/g3log_widget.py index 447d7acb39..a6f155513b 100644 --- a/src/software/thunderscope/log/g3log_widget.py +++ b/src/software/thunderscope/log/g3log_widget.py @@ -1,5 +1,4 @@ from pyqtgraph.Qt.QtWidgets import * -import queue from software.py_constants import * import pyqtgraph.console as pg_console from proto.robot_log_msg_pb2 import RobotLog, LogLevel @@ -63,9 +62,9 @@ def __init__(self, buffer_size: int = 10): def refresh(self) -> None: """Update the log widget with another log message""" # Need to make sure the message is new before logging it - try: - log = self.log_buffer.queue.get_nowait() - except queue.Empty: + + log = self.log_buffer.get(block=False, return_cached=False) + if log is None: return # Checks whether this type of log is enabled from checkboxes diff --git a/src/software/thunderscope/thread_safe_buffer.py b/src/software/thunderscope/thread_safe_buffer.py index 94d37a1299..809c03797a 100644 --- a/src/software/thunderscope/thread_safe_buffer.py +++ b/src/software/thunderscope/thread_safe_buffer.py @@ -26,15 +26,17 @@ def __init__( :param buffer size: The size of the buffer. :param protobuf_type: To buffer - :param log_overrun: False + :param log_overrun: If True, warns when we lose protos during `put` operations """ self.logger = create_logger(protobuf_type.DESCRIPTOR.name + " Buffer") - self.queue = queue.Queue(buffer_size) + self.buffer = queue.Queue(buffer_size) self.protobuf_type = protobuf_type self.log_overrun = log_overrun self.cached_msg = protobuf_type() self.protos_dropped = 0 self.last_logged_protos_dropped = 0 + self.empty_exception = queue.Empty + self.full_exception = queue.Full def get( self, block: bool = False, timeout: float = None, return_cached: bool = True @@ -56,7 +58,7 @@ def get( comes through, or returned the cached msg if return_cached = True :param timeout: If block is True, then wait for this many seconds before throwing an error or returning cached - :param return_cached: If queue is empty, decides whether to + :param return_cached: If buffer is empty, decides whether to return cached value (True) or return None / throw an error (false) :return: protobuf (cached if block is False and there is no data @@ -76,14 +78,14 @@ def get( if block: try: - self.cached_msg = self.queue.get(timeout=timeout) - except queue.Empty as empty: + self.cached_msg = self.buffer.get(timeout=timeout) + except self.empty_exception as empty: if not return_cached: raise empty else: try: - self.cached_msg = self.queue.get_nowait() - except queue.Empty: + self.cached_msg = self.buffer.get_nowait() + except self.empty_exception: if not return_cached: return None @@ -98,10 +100,14 @@ def put(self, proto: Message, block: bool = False, timeout: float = None) -> Non :param timeout: If block is True, then wait for this many seconds """ if block: - self.queue.put(proto, block, timeout) + self.buffer.put(proto, block, timeout) return try: - self.queue.put_nowait(proto) - except queue.Full: + self.buffer.put_nowait(proto) + except self.full_exception: self.protos_dropped += 1 + + def size(self) -> int: + """Returns the number of objects in the buffer""" + return self.buffer.qsize() diff --git a/src/software/thunderscope/thunderscope_main.py b/src/software/thunderscope/thunderscope_main.py index 951780cbb7..839abcd52b 100644 --- a/src/software/thunderscope/thunderscope_main.py +++ b/src/software/thunderscope/thunderscope_main.py @@ -455,7 +455,8 @@ def __ticker(tick_rate_ms: int) -> None: run_sudo=args.sudo, running_in_realtime=(not args.ci_mode), ) as yellow_fs, Gamecontroller( - suppress_logs=(not args.verbose) + suppress_logs=(not args.verbose), + simulator_proto_unix_io=tscope.proto_unix_io_map[ProtoUnixIOTypes.SIM], ) as gamecontroller, ( # Here we only initialize autoref if the --enable_autoref flag is requested. # To avoid nested Python withs, the autoref is initialized as None when this flag doesn't exist.