@@ -2,26 +2,30 @@ use ::rand::RngExt;
22use dashmap:: DashMap ;
33use ghost_sync:: { Client , ServerEvent } ;
44use macroquad:: prelude:: * ;
5+ use std:: sync:: atomic:: { AtomicU64 , Ordering } ;
56use std:: sync:: Arc ;
67use std:: thread;
78use std:: time:: { Duration , Instant } ;
89use uuid:: Uuid ;
910use wincode:: { SchemaRead , SchemaWrite } ;
1011
1112const ADDR : & str = "127.0.0.1:7777" ;
12- const ROOM : & str = "room-128 " ;
13+ const ROOM : & str = "test-room " ;
1314const BOT_COUNT : usize = 128 ;
14- const TICK_RATE : u64 = 20 ;
15- const MAX_SPEED : f32 = 100 .0;
15+ const TICK_RATE : u64 = 60 ;
16+ const MAX_SPEED : f32 = 400 .0;
1617const SCREEN : f32 = 800.0 ;
1718
19+ static BYTES_SENT : AtomicU64 = AtomicU64 :: new ( 0 ) ;
20+ static BYTES_RECV : AtomicU64 = AtomicU64 :: new ( 0 ) ;
21+
1822fn winconfig ( ) -> Conf {
1923 Conf {
2024 window_title : "Bot Stress Test" . into ( ) ,
2125 window_width : SCREEN as i32 ,
2226 window_height : SCREEN as i32 ,
2327 window_resizable : false ,
24- sample_count : BOT_COUNT as i32 ,
28+ sample_count : 512 ,
2529 ..Default :: default ( )
2630 }
2731}
@@ -56,7 +60,7 @@ impl Bot {
5660 #[ inline( always) ]
5761 pub fn update ( & mut self , dt : f32 ) {
5862 let target_pos = self . pos + self . vel * dt;
59- self . pos = self . pos . lerp ( target_pos, 0.225 ) ; // Smooth movement visual
63+ self . pos = self . pos . lerp ( target_pos, 0.225 ) ; // Smooth visual (render only)
6064 self . pos . x = self . pos . x . rem_euclid ( SCREEN ) ;
6165 self . pos . y = self . pos . y . rem_euclid ( SCREEN ) ;
6266 }
@@ -76,12 +80,12 @@ async fn bot_loop(map: BotMap, bot_index: usize) {
7680 let mut client = match Client :: connect ( ADDR ) . await {
7781 Ok ( c) => c,
7882 Err ( e) => {
79- eprintln ! ( "[{bot_index}] connect error: {e}" ) ;
83+ eprintln ! ( "Bot: [{bot_index}] connect error: {e}" ) ;
8084 return ;
8185 }
8286 } ;
8387 if let Err ( e) = client. join ( ROOM ) . await {
84- eprintln ! ( "[{bot_index}] join error: {e}" ) ;
88+ eprintln ! ( "Bot: [{bot_index}] join error: {e}" ) ;
8589 return ;
8690 }
8791
@@ -92,7 +96,7 @@ async fn bot_loop(map: BotMap, bot_index: usize) {
9296
9397 map. insert ( self_id, Bot :: new ( & mut rng) ) ;
9498
95- // Tick rate limiter
99+ // Tick limit to avoid flooding the server with updates. The bot's movement is smoothed
96100 let send_interval = Duration :: from_millis ( 1000 / TICK_RATE ) ;
97101 let mut last_send = Instant :: now ( ) ;
98102
@@ -106,33 +110,41 @@ async fn bot_loop(map: BotMap, bot_index: usize) {
106110 vx : bot. vel . x ,
107111 vy : bot. vel . y ,
108112 } ;
109- let _ = client
110- . broadcast ( & wincode :: serialize ( & payload ) . unwrap ( ) )
111- . await ;
113+ let data = wincode :: serialize ( & payload ) . unwrap ( ) ;
114+ BYTES_SENT . fetch_add ( data . len ( ) as u64 , Ordering :: Relaxed ) ;
115+ let _ = client . broadcast ( & data ) . await ;
112116
113117 bot. random_velo ( & mut rng) ;
114118 }
115119 }
116120
117121 loop {
118- // Draining incoming messages with a short timeout to avoid stalling the loop
119- match tokio:: time:: timeout ( Duration :: from_micros ( 10 ) , client. recv ( ) ) . await {
122+ // Timeout for recv to avoid stalling the bot loop if the server is unresponsive.
123+ // The bot's movement is still updated and rendered during this time
124+ match tokio:: time:: timeout ( Duration :: from_millis ( 100 ) , client. recv ( ) ) . await {
120125 Ok ( Ok ( Some ( ServerEvent :: Broadcast { sender_id, data } ) ) )
121- // Update the bot map, we will render them later
122126 if sender_id != self_id =>
123127 {
128+ BYTES_RECV . fetch_add ( data. len ( ) as u64 , Ordering :: Relaxed ) ;
124129 if let Ok ( v) = wincode:: deserialize :: < Payload > ( & data) {
125130 map. entry ( v. id )
126131 . and_modify ( |b| b. vel = Vec2 :: new ( v. vx , v. vy ) )
127132 . or_insert ( Bot {
128133 pos : Vec2 :: new (
129- rng. random :: < f32 > ( ) * SCREEN ,
130- rng. random :: < f32 > ( ) * SCREEN ,
134+ rng. random_range ( 0.0 .. SCREEN ) ,
135+ rng. random_range ( 0.0 .. SCREEN ) ,
131136 ) ,
132137 vel : Vec2 :: new ( v. vx , v. vy ) ,
133138 } ) ;
134139 }
135140 }
141+ Ok ( Ok ( Some ( ServerEvent :: PlayerLeft { client_id } ) ) ) => {
142+ map. remove ( & client_id) ;
143+ eprintln ! ( "Bot: {client_id} disconnected, removed from map" ) ;
144+ }
145+ Ok ( Ok ( Some ( ServerEvent :: Joined { client_id, .. } ) ) ) => {
146+ eprintln ! ( "Bot: {client_id} joined" ) ;
147+ }
136148 _ => break ,
137149 }
138150 }
@@ -143,28 +155,61 @@ async fn bot_loop(map: BotMap, bot_index: usize) {
143155
144156#[ macroquad:: main( winconfig) ]
145157async fn main ( ) {
146- let map: BotMap = Arc :: new ( DashMap :: new ( ) ) ;
158+ let map: BotMap = Arc :: new ( DashMap :: with_capacity ( BOT_COUNT ) ) ;
147159
148160 for i in 0 ..BOT_COUNT {
149161 let m = map. clone ( ) ;
150- thread:: spawn ( move || {
151- let rt = tokio :: runtime :: Runtime :: new ( ) . unwrap ( ) ;
152- rt . block_on ( bot_loop ( m , i ) ) ;
162+ thread:: spawn ( move || match tokio :: runtime :: Runtime :: new ( ) {
163+ Ok ( rt ) => rt . block_on ( bot_loop ( m , i ) ) ,
164+ Err ( e ) => eprintln ! ( "Tokio runtime error: {e}" ) ,
153165 } ) ;
154166 }
155167
156- let delta = get_frame_time ( ) ;
168+ let mut last_bandwidth = Instant :: now ( ) ;
169+ let mut sent_acc = 0u64 ;
170+ let mut recv_acc = 0u64 ;
157171
158172 loop {
173+ let delta = get_frame_time ( ) ;
174+
159175 clear_background ( BLACK ) ;
176+
177+ let connected = map. len ( ) ;
160178 draw_text (
161- & format ! ( "Bots:{BOT_COUNT} Fps:{:.0}" , 1.0 / delta . max ( 0.001 ) ) ,
179+ & format ! ( "Bots: {connected}/{BOT_COUNT}" ) ,
162180 10.0 ,
163181 24.0 ,
164182 20.0 ,
165183 WHITE ,
166184 ) ;
167- draw_text ( & format ! ( "Tick rate: {TICK_RATE}" ) , 10.0 , 48.0 , 20.0 , WHITE ) ;
185+ draw_text (
186+ & format ! ( "FPS: {:.0}" , 1.0 / delta. max( 0.001 ) ) ,
187+ 10.0 ,
188+ 48.0 ,
189+ 20.0 ,
190+ WHITE ,
191+ ) ;
192+ draw_text ( & format ! ( "Tick rate: {TICK_RATE}" ) , 10.0 , 72.0 , 20.0 , WHITE ) ;
193+
194+ if last_bandwidth. elapsed ( ) . as_secs_f32 ( ) >= 1.0 {
195+ sent_acc = BYTES_SENT . swap ( 0 , Ordering :: Relaxed ) ;
196+ recv_acc = BYTES_RECV . swap ( 0 , Ordering :: Relaxed ) ;
197+ last_bandwidth = Instant :: now ( ) ;
198+ }
199+ draw_text (
200+ & format ! ( "TX: {} KB/s" , sent_acc / 1024 ) ,
201+ 10.0 ,
202+ 96.0 ,
203+ 20.0 ,
204+ GREEN ,
205+ ) ;
206+ draw_text (
207+ & format ! ( "RX: {} KB/s" , recv_acc / 1024 ) ,
208+ 10.0 ,
209+ 120.0 ,
210+ 20.0 ,
211+ BLUE ,
212+ ) ;
168213
169214 for mut entry in map. iter_mut ( ) {
170215 let bot = entry. value_mut ( ) ;
0 commit comments