1- use std:: { net:: SocketAddr , time:: Duration } ;
1+ use std:: { f32 :: consts :: PI , net:: SocketAddr , time:: Duration } ;
22
33use booster:: FallDownStateType ;
4- use hsl_network_messages:: { GameControllerReturnMessage , HulkMessage , StateMessage } ;
4+ use hsl_network_messages:: { GameControllerReturnMessage , Half , HulkMessage , StateMessage } ;
55use ros_z:: time:: Time ;
6- use types:: { messages:: OutgoingMessage , parameters:: HslNetworkParameters } ;
6+ use types:: {
7+ messages:: OutgoingMessage , parameters:: HslNetworkParameters , primary_state:: PrimaryState ,
8+ } ;
79
810use crate :: node:: Blackboard ;
911
@@ -62,27 +64,25 @@ impl Blackboard {
6264
6365 pub fn state_message ( & mut self ) -> Option < OutgoingMessage > {
6466 let now = self . world_state . now ;
65- let remaining_amount_of_messages = self
66- . world_state
67- . filtered_game_controller_state
68- . as_ref ( )
69- . map ( |state| state. remaining_number_of_messages ) ;
67+ let filtered_game_controller_state =
68+ self . world_state . filtered_game_controller_state . as_ref ( ) ?;
7069
7170 if !self . is_state_message_cooldown_elapsed ( now, & self . parameters . hsl_network ) {
7271 return None ;
7372 }
74- if remaining_amount_of_messages. is_none_or ( |remaining_amount_of_messages| {
75- remaining_amount_of_messages
76- < self
77- . parameters
78- . hsl_network
79- . remaining_amount_of_messages_to_stop_sending
80- } ) {
73+ if filtered_game_controller_state. remaining_number_of_messages
74+ < self
75+ . parameters
76+ . hsl_network
77+ . remaining_amount_of_messages_to_stop_sending
78+ {
79+ return None ;
80+ }
81+ if self . world_state . robot . primary_state != PrimaryState :: Playing {
8182 return None ;
8283 }
8384
84- let ground_to_field = self . world_state . robot . ground_to_field . unwrap_or_default ( ) ;
85-
85+ let ground_to_field = self . world_state . robot . ground_to_field ?;
8686 let pose = ground_to_field. as_pose ( ) ;
8787
8888 let ball_position = self
@@ -102,6 +102,28 @@ impl Blackboard {
102102 ball_position,
103103 } ) ;
104104
105+ let max_difference_scale = message_difference_scale (
106+ filtered_game_controller_state. half ,
107+ filtered_game_controller_state. remaining_time_in_half ,
108+ filtered_game_controller_state. remaining_number_of_messages ,
109+ & self . parameters . hsl_network ,
110+ ) ?;
111+
112+ if !is_message_different (
113+ & message,
114+ self . last_sent_hsl_message . as_ref ( ) ,
115+ max_difference_scale,
116+ ) && self
117+ . last_sent_hsl_message_time
118+ . is_some_and ( |last_sent_hsl_message_time| {
119+ now. duration_since ( last_sent_hsl_message_time)
120+ < self . parameters . hsl_network . max_time_since_last_message
121+ } )
122+ {
123+ return None ;
124+ }
125+
126+ self . last_sent_hsl_message = Some ( message) ;
105127 self . last_sent_hsl_message_time = Some ( now) ;
106128
107129 Some ( OutgoingMessage :: Hsl ( message) )
@@ -126,3 +148,206 @@ fn is_cooldown_elapsed(now: Time, last: Option<Time>, cooldown: Duration) -> boo
126148 Some ( last_time) => now. duration_since ( last_time) > cooldown,
127149 }
128150}
151+
152+ fn message_difference_scale (
153+ half : Half ,
154+ mut remaining_time : Duration ,
155+ remaining_number_of_messages : u16 ,
156+ hsl_network_parameters : & HslNetworkParameters ,
157+ ) -> Option < f32 > {
158+ let remaining_messages = remaining_number_of_messages as isize
159+ - hsl_network_parameters. remaining_amount_of_messages_to_stop_sending as isize ;
160+
161+ if half == Half :: First {
162+ remaining_time += hsl_network_parameters. half_duration ;
163+ }
164+ if remaining_messages <= 0 {
165+ return None ;
166+ }
167+
168+ let full_game_duration = hsl_network_parameters. half_duration . mul_f32 ( 2.0 ) ;
169+ if full_game_duration. is_zero ( ) {
170+ return None ;
171+ }
172+
173+ remaining_time = remaining_time. min ( full_game_duration) ;
174+ let expected_remaining_messages = hsl_network_parameters. message_budget as f32
175+ * remaining_time. as_secs_f32 ( )
176+ / full_game_duration. as_secs_f32 ( ) ;
177+ let remaining_message_ratio = expected_remaining_messages / remaining_messages as f32 ;
178+
179+ Some ( hsl_network_parameters. max_message_difference_scale * remaining_message_ratio)
180+ }
181+
182+ fn is_message_different (
183+ message : & HulkMessage ,
184+ last_sent_message : Option < & HulkMessage > ,
185+ max_difference_scale : f32 ,
186+ ) -> bool {
187+ let Some ( last_sent_message) = last_sent_message else {
188+ return true ;
189+ } ;
190+
191+ let ( HulkMessage :: State ( message) , HulkMessage :: State ( last_message) ) =
192+ ( message, last_sent_message) ;
193+ let pose_position_difference = ( message. pose . position ( ) - last_message. pose . position ( ) ) . norm ( ) ;
194+ let pose_angle_difference = angular_difference (
195+ message. pose . orientation ( ) . angle ( ) ,
196+ last_message. pose . orientation ( ) . angle ( ) ,
197+ ) ;
198+
199+ let ball_position_difference = match ( message. ball_position , last_message. ball_position ) {
200+ ( None , None ) => 0.0 ,
201+ ( Some ( left) , Some ( right) ) => ( left. position - right. position ) . norm ( ) ,
202+ _ => f32:: INFINITY ,
203+ } ;
204+
205+ pose_position_difference > max_difference_scale
206+ || pose_angle_difference > max_difference_scale
207+ || ball_position_difference > max_difference_scale
208+ }
209+
210+ fn angular_difference ( from : f32 , to : f32 ) -> f32 {
211+ ( ( from - to + PI ) . rem_euclid ( 2.0 * PI ) - PI ) . abs ( )
212+ }
213+
214+ #[ cfg( test) ]
215+ mod tests {
216+ use std:: time:: Duration ;
217+
218+ use coordinate_systems:: Ground ;
219+ use hsl_network_messages:: PlayerNumber ;
220+ use linear_algebra:: { Isometry2 , Point2 , point, vector} ;
221+ use types:: {
222+ field_dimensions:: FieldDimensions ,
223+ filtered_game_controller_state:: FilteredGameControllerState ,
224+ motion_command:: MotionCommand ,
225+ motion_type:: MotionType ,
226+ parameters:: BehaviorParameters ,
227+ world_state:: { BallState , RobotState , WorldState } ,
228+ } ;
229+
230+ use super :: * ;
231+
232+ #[ test]
233+ fn message_difference_scale_uses_full_game_budget_units ( ) {
234+ let hsl_network_parameters = hsl_network_parameters ( ) ;
235+
236+ let scale = message_difference_scale (
237+ Half :: Second ,
238+ Duration :: from_secs ( 600 ) ,
239+ 600 ,
240+ & hsl_network_parameters,
241+ )
242+ . unwrap ( ) ;
243+
244+ assert ! ( ( scale - 0.3 ) . abs( ) < f32 :: EPSILON ) ;
245+ }
246+
247+ #[ test]
248+ fn state_message_is_sent_for_change_or_heartbeat ( ) {
249+ let mut blackboard = blackboard_for_state_message ( ) ;
250+ allow_recheck_before_timeout ( & mut blackboard) ;
251+
252+ assert ! ( blackboard. state_message( ) . is_some( ) ) ;
253+ blackboard. world_state . now = Time :: from_nanos ( 1_100_000_000 ) ;
254+ assert ! ( blackboard. state_message( ) . is_none( ) ) ;
255+
256+ blackboard. world_state . robot . ground_to_field =
257+ Some ( Isometry2 :: from_parts ( vector ! [ 10.0 , 0.0 ] , 0.0 ) ) ;
258+ assert ! ( blackboard. state_message( ) . is_some( ) ) ;
259+
260+ let mut blackboard = blackboard_for_state_message ( ) ;
261+ allow_recheck_before_timeout ( & mut blackboard) ;
262+ blackboard. world_state . ball = Some ( ball_state ( 0.0 , 0.0 ) ) ;
263+
264+ assert ! ( blackboard. state_message( ) . is_some( ) ) ;
265+ blackboard. world_state . now = Time :: from_nanos ( 1_100_000_000 ) ;
266+ blackboard. world_state . ball = Some ( ball_state ( 10.0 , 0.0 ) ) ;
267+
268+ assert ! ( blackboard. state_message( ) . is_some( ) ) ;
269+
270+ let mut blackboard = blackboard_for_state_message ( ) ;
271+ allow_recheck_before_timeout ( & mut blackboard) ;
272+
273+ assert ! ( blackboard. state_message( ) . is_some( ) ) ;
274+ blackboard. world_state . now = Time :: from_nanos ( 3_000_000_000 ) ;
275+
276+ assert ! ( blackboard. state_message( ) . is_some( ) ) ;
277+ }
278+
279+ fn allow_recheck_before_timeout ( blackboard : & mut Blackboard ) {
280+ blackboard
281+ . parameters
282+ . hsl_network
283+ . hsl_state_message_send_interval = Duration :: from_millis ( 1 ) ;
284+ }
285+
286+ fn hsl_network_parameters ( ) -> HslNetworkParameters {
287+ HslNetworkParameters {
288+ message_budget : 1_200 ,
289+ half_duration : Duration :: from_secs ( 600 ) ,
290+ max_time_since_last_message : Duration :: from_secs ( 2 ) ,
291+ max_message_difference_scale : 0.3 ,
292+ ..Default :: default ( )
293+ }
294+ }
295+
296+ fn ball_state ( x : f32 , y : f32 ) -> BallState {
297+ BallState {
298+ ball_in_ground : point ! [ x, y] ,
299+ ball_in_field : point ! [ x, y] ,
300+ ..Default :: default ( )
301+ }
302+ }
303+
304+ fn blackboard_for_state_message ( ) -> Blackboard {
305+ let mut world_state = WorldState {
306+ now : Time :: from_nanos ( 1_000_000_000 ) ,
307+ filtered_game_controller_state : Some ( FilteredGameControllerState {
308+ half : Half :: Second ,
309+ remaining_time_in_half : Duration :: from_secs ( 600 ) ,
310+ remaining_number_of_messages : 1_200 ,
311+ ..Default :: default ( )
312+ } ) ,
313+ robot : RobotState {
314+ ground_to_field : Some ( Isometry2 :: identity ( ) ) ,
315+ player_number : PlayerNumber :: Three ,
316+ primary_state : PrimaryState :: Playing ,
317+ } ,
318+ ..Default :: default ( )
319+ } ;
320+ world_state. ball = None ;
321+
322+ let mut parameters = BehaviorParameters :: default ( ) ;
323+ parameters. hsl_network = hsl_network_parameters ( ) ;
324+
325+ Blackboard {
326+ field_dimensions : FieldDimensions :: SPL_2025 ,
327+ parameters,
328+ world_state,
329+ path_obstacles_output : Vec :: new ( ) ,
330+ time_since_last_switch : Duration :: ZERO ,
331+ direction_difference : 0.0 ,
332+ voronoi_inputs : Vec :: new ( ) ,
333+ ball : None ,
334+ last_ball : None ,
335+ last_close_enough_to_kick : false ,
336+ last_kick_target : None ,
337+ last_motion_command : MotionCommand :: default ( ) ,
338+ last_motion_switch_time : Time :: zero ( ) ,
339+ last_motion_type : None :: < MotionType > ,
340+ last_sent_game_controller_return_message_time : None ,
341+ last_sent_hsl_message : None ,
342+ last_sent_hsl_message_time : None ,
343+ last_closest_to_ball : false ,
344+ closest_to_ball_entered_area_since : None ,
345+ closest_to_ball_left_area_since : None ,
346+ is_injected_motion_command : false ,
347+ walk_position : None :: < Point2 < Ground > > ,
348+ body_motion : None ,
349+ head_motion : None ,
350+ voronoi_map : None ,
351+ }
352+ }
353+ }
0 commit comments