Skip to content

Commit 51a21e2

Browse files
committed
feat: reduce HSL state messages
1 parent 33d9b5a commit 51a21e2

14 files changed

Lines changed: 296 additions & 25 deletions

File tree

crates/bevyhavior_simulator/src/behavior_runtime.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ fn create_behavior_blackboard(parameters: BehaviorParameters) -> BehaviorBlackbo
171171
last_motion_switch_time: ros_z::time::Time::zero(),
172172
last_motion_type: None,
173173
last_sent_game_controller_return_message_time: None,
174+
last_sent_hsl_message: None,
174175
last_sent_hsl_message_time: None,
175176
last_closest_to_ball: false,
176177
closest_to_ball_entered_area_since: None,

crates/bevyhavior_simulator/src/game_controller.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::time::{Duration, SystemTime};
22

33
use bevy::prelude::*;
4-
use hsl_network_messages::{GamePhase, GameState, PlayerNumber, Team, TeamColor, TeamState};
4+
use hsl_network_messages::{GamePhase, GameState, Half, PlayerNumber, Team, TeamColor, TeamState};
55
use types::{
66
field_dimensions::GlobalFieldSide, filtered_game_controller_state::FilteredGameControllerState,
77
filtered_game_state::FilteredGameState, game_controller_state::GameControllerState,
@@ -80,6 +80,7 @@ pub fn default_game_controller_state() -> GameControllerState {
8080
game_state: GameState::Playing,
8181
stopped: false,
8282
game_phase: GamePhase::Normal,
83+
half: Half::First,
8384
remaining_time_in_half: Duration::ZERO,
8485
kicking_team: Some(Team::Hulks),
8586
last_game_state_change: SystemTime::UNIX_EPOCH,
@@ -140,6 +141,7 @@ pub fn filtered_game_controller_state_for_team(
140141
FilteredGameControllerState {
141142
game_state: filtered_game_state_from(game_controller_state),
142143
opponent_game_state: filtered_game_state_from(game_controller_state),
144+
half: game_controller_state.half,
143145
remaining_time_in_half: game_controller_state.remaining_time_in_half,
144146
game_phase: game_controller_state.game_phase,
145147
kicking_team: game_controller_state

crates/nodes/behavior_node/src/node.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use booster::FallDownState;
44
use color_eyre::Result;
55

66
use coordinate_systems::{Field, Ground};
7-
use hsl_network_messages::PlayerNumber;
7+
use hsl_network_messages::{HulkMessage, PlayerNumber};
88
use linear_algebra::{Isometry2, Point2, Pose2, Vector2};
99
use ros_z::{prelude::*, qos::QosDurability, time::Time};
1010
use serde::{Deserialize, Serialize};
@@ -57,6 +57,7 @@ pub struct Blackboard {
5757
pub last_motion_switch_time: Time,
5858
pub last_motion_type: Option<MotionType>,
5959
pub last_sent_game_controller_return_message_time: Option<Time>,
60+
pub last_sent_hsl_message: Option<HulkMessage>,
6061
pub last_sent_hsl_message_time: Option<Time>,
6162
pub last_closest_to_ball: bool,
6263
pub closest_to_ball_entered_area_since: Option<Time>,
@@ -268,6 +269,7 @@ pub async fn run(ctx: Arc<Context>) -> Result<()> {
268269
last_motion_switch_time: Time::zero(),
269270
last_motion_type: None,
270271
last_sent_game_controller_return_message_time: None,
272+
last_sent_hsl_message: None,
271273
last_sent_hsl_message_time: None,
272274
last_closest_to_ball: false,
273275
closest_to_ball_entered_area_since: None,

crates/nodes/behavior_node/src/send_message.rs

Lines changed: 242 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
use std::{net::SocketAddr, time::Duration};
1+
use std::{f32::consts::PI, net::SocketAddr, time::Duration};
22

33
use booster::FallDownStateType;
4-
use hsl_network_messages::{GameControllerReturnMessage, HulkMessage, StateMessage};
4+
use hsl_network_messages::{GameControllerReturnMessage, Half, HulkMessage, StateMessage};
55
use ros_z::time::Time;
6-
use types::{messages::OutgoingMessage, parameters::HslNetworkParameters};
6+
use types::{
7+
messages::OutgoingMessage, parameters::HslNetworkParameters, primary_state::PrimaryState,
8+
};
79

810
use 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+
}

crates/nodes/game_controller_filter/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ impl GameControllerFilter {
104104
game_state: message.game_state,
105105
stopped: message.stopped,
106106
game_phase: message.game_phase,
107+
half: message.half,
107108
remaining_time_in_half: message.remaining_time_in_half,
108109
kicking_team: message.kicking_team,
109110
last_game_state_change: self.last_game_state_change.unwrap().to_wallclock(),

crates/nodes/game_controller_state_filter/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ impl GameControllerStateFilter {
210210
FilteredGameControllerState {
211211
game_state: game_states.own,
212212
opponent_game_state: game_states.opponent,
213+
half: game_controller_state.half,
213214
remaining_time_in_half: game_controller_state.remaining_time_in_half,
214215
game_phase: game_controller_state.game_phase,
215216
kicking_team,

crates/types/src/filtered_game_controller_state.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{collections::HashMap, time::Duration};
22

3-
use hsl_network_messages::{GamePhase, Penalty, PlayerNumber, SubState, Team};
3+
use hsl_network_messages::{GamePhase, Half, Penalty, PlayerNumber, SubState, Team};
44
use path_serde::{PathIntrospect, PathSerialize};
55
use ros_z::Message;
66
use serde::{Deserialize, Serialize};
@@ -15,6 +15,7 @@ use crate::{
1515
pub struct FilteredGameControllerState {
1616
pub game_state: FilteredGameState,
1717
pub opponent_game_state: FilteredGameState,
18+
pub half: Half,
1819
pub remaining_time_in_half: Duration,
1920
pub game_phase: GamePhase,
2021
pub kicking_team: Option<Team>,
@@ -32,6 +33,7 @@ impl Default for FilteredGameControllerState {
3233
Self {
3334
game_state: Default::default(),
3435
opponent_game_state: Default::default(),
36+
half: Half::Second,
3537
remaining_time_in_half: Duration::ZERO,
3638
game_phase: Default::default(),
3739
kicking_team: Default::default(),

0 commit comments

Comments
 (0)