1
2use glam::{Vec2, Vec3, Vec4};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum NetworkTopology {
14 ClientServer,
15 PeerToPeer,
16 Dedicated,
17 ListenServer,
18 Relay,
19 Hybrid,
20}
21
22impl NetworkTopology {
23 pub fn label(&self) -> &'static str {
24 match self {
25 Self::ClientServer => "Client-Server",
26 Self::PeerToPeer => "Peer-to-Peer",
27 Self::Dedicated => "Dedicated Server",
28 Self::ListenServer => "Listen Server",
29 Self::Relay => "Relay",
30 Self::Hybrid => "Hybrid",
31 }
32 }
33 pub fn description(&self) -> &'static str {
34 match self {
35 Self::ClientServer => "Authoritative server with multiple clients",
36 Self::PeerToPeer => "Direct peer connections, no central server",
37 Self::Dedicated => "Headless dedicated server process",
38 Self::ListenServer => "One client acts as host",
39 Self::Relay => "All traffic routed through relay server",
40 Self::Hybrid => "Mix of server-authority and P2P for low-latency data",
41 }
42 }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50pub enum ReplicationMode {
51 Unreliable,
52 Reliable,
53 ReliableOrdered,
54 ReliableSequenced,
55 Multicast,
56 OwnerOnly,
57 ServerOnly,
58 Skip,
59}
60
61impl ReplicationMode {
62 pub fn label(&self) -> &'static str {
63 match self {
64 Self::Unreliable => "Unreliable",
65 Self::Reliable => "Reliable",
66 Self::ReliableOrdered => "Reliable Ordered",
67 Self::ReliableSequenced => "Reliable Sequenced",
68 Self::Multicast => "Multicast",
69 Self::OwnerOnly => "Owner Only",
70 Self::ServerOnly => "Server Only",
71 Self::Skip => "No Replication",
72 }
73 }
74
75 pub fn uses_sequence_numbers(&self) -> bool {
76 matches!(self, Self::ReliableOrdered | Self::ReliableSequenced)
77 }
78
79 pub fn is_reliable(&self) -> bool {
80 matches!(self, Self::Reliable | Self::ReliableOrdered | Self::ReliableSequenced)
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85pub enum ReplicatedPropertyType {
86 Bool,
87 Byte,
88 Int32,
89 Int64,
90 Float,
91 Double,
92 Vector3,
93 Quaternion,
94 String,
95 Transform,
96 Color,
97 ObjectRef,
98 Custom,
99}
100
101impl ReplicatedPropertyType {
102 pub fn label(&self) -> &'static str {
103 match self {
104 Self::Bool => "bool",
105 Self::Byte => "byte",
106 Self::Int32 => "int32",
107 Self::Int64 => "int64",
108 Self::Float => "float",
109 Self::Double => "double",
110 Self::Vector3 => "Vector3",
111 Self::Quaternion => "Quaternion",
112 Self::String => "string",
113 Self::Transform => "Transform",
114 Self::Color => "Color",
115 Self::ObjectRef => "Object Reference",
116 Self::Custom => "Custom",
117 }
118 }
119
120 pub fn size_bytes(&self) -> usize {
121 match self {
122 Self::Bool | Self::Byte => 1,
123 Self::Int32 | Self::Float => 4,
124 Self::Int64 | Self::Double => 8,
125 Self::Vector3 => 12,
126 Self::Quaternion | Self::Color | Self::Transform => 16,
127 Self::String => 64, Self::ObjectRef => 8,
129 Self::Custom => 0,
130 }
131 }
132}
133
134#[derive(Debug, Clone)]
135pub struct ReplicatedProperty {
136 pub name: String,
137 pub prop_type: ReplicatedPropertyType,
138 pub replication_mode: ReplicationMode,
139 pub update_rate_hz: f32,
140 pub send_on_change_only: bool,
141 pub priority: f32,
142 pub relevancy_distance: f32,
143 pub use_delta_compression: bool,
144 pub custom_serializer: bool,
145 pub size_bytes: usize,
146 pub dirty: bool,
147 pub last_sent_frame: u64,
148}
149
150impl ReplicatedProperty {
151 pub fn new(name: &str, prop_type: ReplicatedPropertyType) -> Self {
152 let size_bytes = prop_type.size_bytes();
153 Self {
154 name: name.to_string(),
155 prop_type,
156 replication_mode: ReplicationMode::Unreliable,
157 update_rate_hz: 30.0,
158 send_on_change_only: true,
159 priority: 1.0,
160 relevancy_distance: 0.0,
161 use_delta_compression: false,
162 custom_serializer: false,
163 size_bytes,
164 dirty: false,
165 last_sent_frame: 0,
166 }
167 }
168
169 pub fn bits_per_second(&self) -> f32 {
170 self.size_bytes as f32 * 8.0 * self.update_rate_hz
171 }
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
179pub enum RpcTarget {
180 Server,
181 AllClients,
182 AllClientsIncludingSelf,
183 OwnerClient,
184 MulticastReliable,
185 MulticastUnreliable,
186}
187
188impl RpcTarget {
189 pub fn label(&self) -> &'static str {
190 match self {
191 Self::Server => "Server",
192 Self::AllClients => "All Clients",
193 Self::AllClientsIncludingSelf => "All Clients (incl. Self)",
194 Self::OwnerClient => "Owner Client",
195 Self::MulticastReliable => "Multicast Reliable",
196 Self::MulticastUnreliable => "Multicast Unreliable",
197 }
198 }
199}
200
201#[derive(Debug, Clone)]
202pub struct RpcParam {
203 pub name: String,
204 pub param_type: ReplicatedPropertyType,
205}
206
207#[derive(Debug, Clone)]
208pub struct RpcDefinition {
209 pub name: String,
210 pub target: RpcTarget,
211 pub params: Vec<RpcParam>,
212 pub reliable: bool,
213 pub requires_authority: bool,
214 pub rate_limit_hz: f32,
215 pub estimated_bytes: usize,
216}
217
218impl RpcDefinition {
219 pub fn new(name: &str, target: RpcTarget) -> Self {
220 Self {
221 name: name.to_string(),
222 target,
223 params: Vec::new(),
224 reliable: true,
225 requires_authority: false,
226 rate_limit_hz: 0.0,
227 estimated_bytes: 8, }
229 }
230
231 pub fn add_param(mut self, name: &str, param_type: ReplicatedPropertyType) -> Self {
232 self.estimated_bytes += param_type.size_bytes();
233 self.params.push(RpcParam { name: name.to_string(), param_type });
234 self
235 }
236
237 pub fn call_signature(&self) -> String {
238 let params: Vec<String> = self.params.iter().map(|p| format!("{}: {}", p.name, p.param_type.label())).collect();
239 format!("{}({}) -> {}", self.name, params.join(", "), self.target.label())
240 }
241}
242
243#[derive(Debug, Clone)]
248pub struct ReplicatedActor {
249 pub class_name: String,
250 pub properties: Vec<ReplicatedProperty>,
251 pub rpcs: Vec<RpcDefinition>,
252 pub net_update_frequency: f32,
253 pub net_cull_distance_squared: f32,
254 pub bnet_use_owner_relevancy: bool,
255 pub always_relevant: bool,
256 pub relevancy_method: RelevancyMethod,
257 pub bandwidth_bytes_per_sec: f32,
258 pub priority: f32,
259}
260
261#[derive(Debug, Clone, Copy, PartialEq)]
262pub enum RelevancyMethod {
263 Distance,
264 AlwaysRelevant,
265 OwnerAndViewer,
266 TeammatesOnly,
267 Custom,
268}
269
270impl RelevancyMethod {
271 pub fn label(&self) -> &'static str {
272 match self {
273 Self::Distance => "Distance",
274 Self::AlwaysRelevant => "Always Relevant",
275 Self::OwnerAndViewer => "Owner & Viewer",
276 Self::TeammatesOnly => "Teammates Only",
277 Self::Custom => "Custom",
278 }
279 }
280}
281
282impl ReplicatedActor {
283 pub fn new(class_name: &str) -> Self {
284 Self {
285 class_name: class_name.to_string(),
286 properties: Vec::new(),
287 rpcs: Vec::new(),
288 net_update_frequency: 30.0,
289 net_cull_distance_squared: 150.0 * 150.0,
290 bnet_use_owner_relevancy: false,
291 always_relevant: false,
292 relevancy_method: RelevancyMethod::Distance,
293 bandwidth_bytes_per_sec: 0.0,
294 priority: 1.0,
295 }
296 }
297
298 pub fn with_property(mut self, prop: ReplicatedProperty) -> Self {
299 self.bandwidth_bytes_per_sec += prop.bits_per_second() / 8.0;
300 self.properties.push(prop);
301 self
302 }
303
304 pub fn with_rpc(mut self, rpc: RpcDefinition) -> Self {
305 self.rpcs.push(rpc);
306 self
307 }
308
309 pub fn total_bandwidth_bps(&self) -> f32 {
310 self.properties.iter().map(|p| p.bits_per_second()).sum()
311 }
312
313 pub fn generate_replication_code(&self) -> String {
314 let mut lines = Vec::new();
315 lines.push(format!("// Replicated properties for {}", self.class_name));
316 for prop in &self.properties {
317 lines.push(format!("// [Replicated({:?})] pub {}: {},", prop.replication_mode, prop.name, prop.prop_type.label()));
318 }
319 lines.push(String::new());
320 lines.push("// RPCs:".to_string());
321 for rpc in &self.rpcs {
322 lines.push(format!("// [RPC({:?})] fn {};", rpc.target, rpc.call_signature()));
323 }
324 lines.join("\n")
325 }
326
327 pub fn player_actor() -> Self {
328 Self::new("PlayerController")
329 .with_property({
330 let mut p = ReplicatedProperty::new("position", ReplicatedPropertyType::Vector3);
331 p.update_rate_hz = 60.0;
332 p.use_delta_compression = true;
333 p
334 })
335 .with_property({
336 let mut p = ReplicatedProperty::new("rotation", ReplicatedPropertyType::Quaternion);
337 p.update_rate_hz = 60.0;
338 p
339 })
340 .with_property({
341 let mut p = ReplicatedProperty::new("health", ReplicatedPropertyType::Float);
342 p.replication_mode = ReplicationMode::Reliable;
343 p.send_on_change_only = true;
344 p
345 })
346 .with_property({
347 let mut p = ReplicatedProperty::new("ammo", ReplicatedPropertyType::Int32);
348 p.replication_mode = ReplicationMode::ReliableOrdered;
349 p.send_on_change_only = true;
350 p
351 })
352 .with_rpc(
353 RpcDefinition::new("ServerFire", RpcTarget::Server)
354 .add_param("origin", ReplicatedPropertyType::Vector3)
355 .add_param("direction", ReplicatedPropertyType::Vector3)
356 )
357 .with_rpc(
358 RpcDefinition::new("ClientPlayHitEffect", RpcTarget::OwnerClient)
359 .add_param("hit_position", ReplicatedPropertyType::Vector3)
360 )
361 .with_rpc(
362 RpcDefinition::new("MulticastExplosion", RpcTarget::MulticastUnreliable)
363 .add_param("position", ReplicatedPropertyType::Vector3)
364 .add_param("radius", ReplicatedPropertyType::Float)
365 )
366 }
367}
368
369#[derive(Debug, Clone)]
374pub struct LagSimulation {
375 pub enabled: bool,
376 pub latency_ms: f32,
377 pub jitter_ms: f32,
378 pub packet_loss_pct: f32,
379 pub duplication_pct: f32,
380 pub reorder_pct: f32,
381 pub bandwidth_kbps: u32,
382 pub profile: LagProfile,
383}
384
385#[derive(Debug, Clone, Copy, PartialEq)]
386pub enum LagProfile {
387 None,
388 Good,
389 Average,
390 Poor,
391 Terrible,
392 Mobile3G,
393 Mobile4G,
394 Wifi,
395 Custom,
396}
397
398impl LagProfile {
399 pub fn label(&self) -> &'static str {
400 match self {
401 Self::None => "No Lag",
402 Self::Good => "Good (20ms)",
403 Self::Average => "Average (60ms)",
404 Self::Poor => "Poor (150ms)",
405 Self::Terrible => "Terrible (300ms+)",
406 Self::Mobile3G => "Mobile 3G",
407 Self::Mobile4G => "Mobile 4G",
408 Self::Wifi => "WiFi",
409 Self::Custom => "Custom",
410 }
411 }
412
413 pub fn apply_to_sim(&self, sim: &mut LagSimulation) {
414 match self {
415 Self::None => { sim.latency_ms = 0.0; sim.jitter_ms = 0.0; sim.packet_loss_pct = 0.0; sim.bandwidth_kbps = 100_000; }
416 Self::Good => { sim.latency_ms = 20.0; sim.jitter_ms = 2.0; sim.packet_loss_pct = 0.0; sim.bandwidth_kbps = 10_000; }
417 Self::Average => { sim.latency_ms = 60.0; sim.jitter_ms = 10.0; sim.packet_loss_pct = 0.5; sim.bandwidth_kbps = 5_000; }
418 Self::Poor => { sim.latency_ms = 150.0; sim.jitter_ms = 30.0; sim.packet_loss_pct = 2.0; sim.bandwidth_kbps = 1_000; }
419 Self::Terrible => { sim.latency_ms = 300.0; sim.jitter_ms = 80.0; sim.packet_loss_pct = 5.0; sim.bandwidth_kbps = 256; }
420 Self::Mobile3G => { sim.latency_ms = 100.0; sim.jitter_ms = 20.0; sim.packet_loss_pct = 1.0; sim.bandwidth_kbps = 384; }
421 Self::Mobile4G => { sim.latency_ms = 40.0; sim.jitter_ms = 8.0; sim.packet_loss_pct = 0.2; sim.bandwidth_kbps = 5_000; }
422 Self::Wifi => { sim.latency_ms = 5.0; sim.jitter_ms = 5.0; sim.packet_loss_pct = 0.1; sim.bandwidth_kbps = 20_000; }
423 Self::Custom => {}
424 }
425 sim.profile = *self;
426 }
427}
428
429impl Default for LagSimulation {
430 fn default() -> Self {
431 Self {
432 enabled: false,
433 latency_ms: 0.0,
434 jitter_ms: 0.0,
435 packet_loss_pct: 0.0,
436 duplication_pct: 0.0,
437 reorder_pct: 0.0,
438 bandwidth_kbps: 100_000,
439 profile: LagProfile::None,
440 }
441 }
442}
443
444impl LagSimulation {
445 pub fn rtt_ms(&self) -> f32 { self.latency_ms * 2.0 }
446 pub fn bandwidth_bytes_per_sec(&self) -> f32 { self.bandwidth_kbps as f32 * 128.0 }
447}
448
449#[derive(Debug, Clone, Default)]
454pub struct BandwidthSample {
455 pub timestamp_ms: f64,
456 pub bytes_sent: u64,
457 pub bytes_recv: u64,
458 pub packets_sent: u32,
459 pub packets_recv: u32,
460 pub packets_lost: u32,
461 pub rtt_ms: f32,
462}
463
464#[derive(Debug, Clone)]
465pub struct BandwidthMonitor {
466 pub samples: Vec<BandwidthSample>,
467 pub max_samples: usize,
468 pub total_bytes_sent: u64,
469 pub total_bytes_recv: u64,
470 pub total_packets_lost: u64,
471 pub session_start_ms: f64,
472}
473
474impl Default for BandwidthMonitor {
475 fn default() -> Self {
476 Self {
477 samples: Vec::new(),
478 max_samples: 512,
479 total_bytes_sent: 0,
480 total_bytes_recv: 0,
481 total_packets_lost: 0,
482 session_start_ms: 0.0,
483 }
484 }
485}
486
487impl BandwidthMonitor {
488 pub fn push_sample(&mut self, sample: BandwidthSample) {
489 self.total_bytes_sent += sample.bytes_sent;
490 self.total_bytes_recv += sample.bytes_recv;
491 self.total_packets_lost += sample.packets_lost as u64;
492 self.samples.push(sample);
493 if self.samples.len() > self.max_samples {
494 self.samples.remove(0);
495 }
496 }
497
498 pub fn current_send_kbps(&self) -> f32 {
499 if self.samples.len() < 2 { return 0.0; }
500 let n = self.samples.len();
501 let bytes = self.samples[n-1].bytes_sent;
502 let dt_s = (self.samples[n-1].timestamp_ms - self.samples[n-2].timestamp_ms) / 1000.0;
503 if dt_s <= 0.0 { return 0.0; }
504 bytes as f32 / dt_s as f32 / 1024.0
505 }
506
507 pub fn current_recv_kbps(&self) -> f32 {
508 if self.samples.len() < 2 { return 0.0; }
509 let n = self.samples.len();
510 let bytes = self.samples[n-1].bytes_recv;
511 let dt_s = (self.samples[n-1].timestamp_ms - self.samples[n-2].timestamp_ms) / 1000.0;
512 if dt_s <= 0.0 { return 0.0; }
513 bytes as f32 / dt_s as f32 / 1024.0
514 }
515
516 pub fn avg_rtt_ms(&self) -> f32 {
517 if self.samples.is_empty() { return 0.0; }
518 self.samples.iter().map(|s| s.rtt_ms).sum::<f32>() / self.samples.len() as f32
519 }
520
521 pub fn packet_loss_pct(&self) -> f32 {
522 let total_sent: u64 = self.samples.iter().map(|s| s.packets_sent as u64).sum();
523 if total_sent == 0 { return 0.0; }
524 self.total_packets_lost as f32 / total_sent as f32 * 100.0
525 }
526
527 pub fn generate_synthetic_samples(&mut self, count: usize) {
528 let mut rng_seed: u64 = 42;
529 for i in 0..count {
530 rng_seed = rng_seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
531 let noise = ((rng_seed >> 33) as f32) / (u32::MAX as f32) * 2.0 - 1.0;
532 self.push_sample(BandwidthSample {
533 timestamp_ms: i as f64 * 16.666,
534 bytes_sent: (1024 + (noise * 200.0) as i64).max(0) as u64,
535 bytes_recv: (512 + (noise * 100.0) as i64).max(0) as u64,
536 packets_sent: 8,
537 packets_recv: 8,
538 packets_lost: if noise > 0.9 { 1 } else { 0 },
539 rtt_ms: 45.0 + noise * 10.0,
540 });
541 }
542 }
543}
544
545#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
550pub enum SessionState {
551 Creating,
552 Searching,
553 Joining,
554 InLobby,
555 InGame,
556 Ending,
557 Disconnected,
558}
559
560impl SessionState {
561 pub fn label(&self) -> &'static str {
562 match self {
563 Self::Creating => "Creating",
564 Self::Searching => "Searching",
565 Self::Joining => "Joining",
566 Self::InLobby => "In Lobby",
567 Self::InGame => "In Game",
568 Self::Ending => "Ending",
569 Self::Disconnected => "Disconnected",
570 }
571 }
572}
573
574#[derive(Debug, Clone)]
575pub struct NetworkPlayer {
576 pub player_id: u64,
577 pub display_name: String,
578 pub ping_ms: u32,
579 pub is_host: bool,
580 pub is_local: bool,
581 pub is_ready: bool,
582 pub team: Option<u32>,
583 pub connection_quality: ConnectionQuality,
584 pub bytes_sent: u64,
585 pub bytes_recv: u64,
586 pub packet_loss_pct: f32,
587 pub load_pct: f32,
588}
589
590#[derive(Debug, Clone, Copy, PartialEq)]
591pub enum ConnectionQuality {
592 Excellent,
593 Good,
594 Fair,
595 Poor,
596 Disconnecting,
597}
598
599impl ConnectionQuality {
600 pub fn from_ping(ping_ms: u32) -> Self {
601 match ping_ms {
602 0..=40 => Self::Excellent,
603 41..=80 => Self::Good,
604 81..=150 => Self::Fair,
605 151..=300 => Self::Poor,
606 _ => Self::Disconnecting,
607 }
608 }
609
610 pub fn color(&self) -> Vec4 {
611 match self {
612 Self::Excellent => Vec4::new(0.2, 0.9, 0.2, 1.0),
613 Self::Good => Vec4::new(0.6, 0.9, 0.2, 1.0),
614 Self::Fair => Vec4::new(0.9, 0.8, 0.1, 1.0),
615 Self::Poor => Vec4::new(0.9, 0.4, 0.1, 1.0),
616 Self::Disconnecting => Vec4::new(0.9, 0.1, 0.1, 1.0),
617 }
618 }
619}
620
621#[derive(Debug, Clone)]
622pub struct NetworkSession {
623 pub session_id: String,
624 pub session_name: String,
625 pub topology: NetworkTopology,
626 pub state: SessionState,
627 pub max_players: u32,
628 pub players: Vec<NetworkPlayer>,
629 pub map_name: String,
630 pub game_mode: String,
631 pub is_private: bool,
632 pub server_region: String,
633 pub server_ip: String,
634 pub server_port: u16,
635 pub tick_rate: u32,
636 pub uptime_secs: f64,
637}
638
639impl NetworkSession {
640 pub fn new_lobby(name: &str) -> Self {
641 Self {
642 session_id: format!("session_{}", name.to_lowercase().replace(' ', "_")),
643 session_name: name.to_string(),
644 topology: NetworkTopology::Dedicated,
645 state: SessionState::InLobby,
646 max_players: 16,
647 players: Vec::new(),
648 map_name: "MainMap".to_string(),
649 game_mode: "Team Deathmatch".to_string(),
650 is_private: false,
651 server_region: "us-east-1".to_string(),
652 server_ip: "192.168.1.100".to_string(),
653 server_port: 7777,
654 tick_rate: 60,
655 uptime_secs: 0.0,
656 }
657 }
658
659 pub fn player_count(&self) -> u32 { self.players.len() as u32 }
660 pub fn is_full(&self) -> bool { self.player_count() >= self.max_players }
661
662 pub fn average_ping(&self) -> f32 {
663 if self.players.is_empty() { return 0.0; }
664 self.players.iter().map(|p| p.ping_ms as f32).sum::<f32>() / self.players.len() as f32
665 }
666
667 pub fn host_player(&self) -> Option<&NetworkPlayer> {
668 self.players.iter().find(|p| p.is_host)
669 }
670
671 pub fn all_ready(&self) -> bool {
672 self.players.iter().filter(|p| !p.is_host).all(|p| p.is_ready)
673 }
674
675 pub fn total_bandwidth_kbps(&self) -> f32 {
676 self.players.iter().map(|p| (p.bytes_sent + p.bytes_recv) as f32 / 1024.0).sum()
677 }
678}
679
680#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
685pub enum NetNodeKind {
686 Client,
687 Server,
688 Relay,
689 LoadBalancer,
690 Database,
691 Matchmaker,
692 AuthServer,
693 VoiceServer,
694 Metrics,
695 Gateway,
696}
697
698impl NetNodeKind {
699 pub fn label(&self) -> &'static str {
700 match self {
701 Self::Client => "Client",
702 Self::Server => "Server",
703 Self::Relay => "Relay",
704 Self::LoadBalancer => "Load Balancer",
705 Self::Database => "Database",
706 Self::Matchmaker => "Matchmaker",
707 Self::AuthServer => "Auth Server",
708 Self::VoiceServer => "Voice Server",
709 Self::Metrics => "Metrics",
710 Self::Gateway => "Gateway",
711 }
712 }
713
714 pub fn color(&self) -> Vec4 {
715 match self {
716 Self::Client => Vec4::new(0.3, 0.6, 0.9, 1.0),
717 Self::Server => Vec4::new(0.9, 0.6, 0.2, 1.0),
718 Self::Relay => Vec4::new(0.5, 0.8, 0.5, 1.0),
719 Self::LoadBalancer => Vec4::new(0.8, 0.5, 0.8, 1.0),
720 Self::Database => Vec4::new(0.8, 0.4, 0.3, 1.0),
721 Self::Matchmaker => Vec4::new(0.4, 0.8, 0.8, 1.0),
722 Self::AuthServer => Vec4::new(0.8, 0.8, 0.3, 1.0),
723 Self::VoiceServer => Vec4::new(0.6, 0.6, 0.9, 1.0),
724 Self::Metrics => Vec4::new(0.6, 0.9, 0.6, 1.0),
725 Self::Gateway => Vec4::new(0.9, 0.7, 0.5, 1.0),
726 }
727 }
728}
729
730#[derive(Debug, Clone)]
731pub struct NetNode {
732 pub id: u64,
733 pub kind: NetNodeKind,
734 pub position: Vec2,
735 pub size: Vec2,
736 pub name: String,
737 pub region: String,
738 pub instance_count: u32,
739 pub player_count: u32,
740 pub cpu_usage: f32,
741 pub mem_usage_mb: f32,
742 pub bandwidth_kbps: f32,
743 pub status_ok: bool,
744}
745
746impl NetNode {
747 pub fn new(id: u64, kind: NetNodeKind, position: Vec2) -> Self {
748 Self {
749 id,
750 kind,
751 position,
752 size: Vec2::new(140.0, 60.0),
753 name: kind.label().to_string(),
754 region: "us-east-1".to_string(),
755 instance_count: 1,
756 player_count: 0,
757 cpu_usage: 0.0,
758 mem_usage_mb: 0.0,
759 bandwidth_kbps: 0.0,
760 status_ok: true,
761 }
762 }
763}
764
765#[derive(Debug, Clone)]
766pub struct NetEdge {
767 pub id: u64,
768 pub from_id: u64,
769 pub to_id: u64,
770 pub protocol: NetworkProtocol,
771 pub latency_ms: f32,
772 pub bandwidth_kbps: f32,
773 pub encrypted: bool,
774 pub bidirectional: bool,
775}
776
777#[derive(Debug, Clone, Copy, PartialEq)]
778pub enum NetworkProtocol {
779 Udp,
780 Tcp,
781 WebSocket,
782 WebRtc,
783 Quic,
784 Rudp,
785}
786
787impl NetworkProtocol {
788 pub fn label(&self) -> &'static str {
789 match self {
790 Self::Udp => "UDP",
791 Self::Tcp => "TCP",
792 Self::WebSocket => "WebSocket",
793 Self::WebRtc => "WebRTC",
794 Self::Quic => "QUIC",
795 Self::Rudp => "RUDP",
796 }
797 }
798 pub fn is_reliable(&self) -> bool {
799 matches!(self, Self::Tcp | Self::WebSocket | Self::Quic)
800 }
801}
802
803#[derive(Debug, Clone)]
804pub struct NetworkTopologyGraph {
805 pub nodes: Vec<NetNode>,
806 pub edges: Vec<NetEdge>,
807 pub next_id: u64,
808}
809
810impl Default for NetworkTopologyGraph {
811 fn default() -> Self {
812 let mut g = Self { nodes: Vec::new(), edges: Vec::new(), next_id: 1 };
813 g.build_typical_dedicated_topology();
814 g
815 }
816}
817
818impl NetworkTopologyGraph {
819 fn next_id(&mut self) -> u64 {
820 let id = self.next_id;
821 self.next_id += 1;
822 id
823 }
824
825 pub fn add_node(&mut self, kind: NetNodeKind, pos: Vec2, name: &str) -> u64 {
826 let id = self.next_id();
827 let mut node = NetNode::new(id, kind, pos);
828 node.name = name.to_string();
829 self.nodes.push(node);
830 id
831 }
832
833 pub fn add_edge(&mut self, from: u64, to: u64, protocol: NetworkProtocol) -> u64 {
834 let id = self.next_id();
835 self.edges.push(NetEdge {
836 id,
837 from_id: from,
838 to_id: to,
839 protocol,
840 latency_ms: 20.0,
841 bandwidth_kbps: 10_000.0,
842 encrypted: true,
843 bidirectional: true,
844 });
845 id
846 }
847
848 pub fn build_typical_dedicated_topology(&mut self) {
849 let gw = self.add_node(NetNodeKind::Gateway, Vec2::new(400.0, 20.0), "CDN Gateway");
850 let auth = self.add_node(NetNodeKind::AuthServer, Vec2::new(100.0, 120.0), "Auth Service");
851 let match_ = self.add_node(NetNodeKind::Matchmaker, Vec2::new(400.0, 120.0), "Matchmaker");
852 let lb = self.add_node(NetNodeKind::LoadBalancer, Vec2::new(700.0, 120.0), "Load Balancer");
853 let db = self.add_node(NetNodeKind::Database, Vec2::new(100.0, 260.0), "Game DB");
854 let srv1 = self.add_node(NetNodeKind::Server, Vec2::new(550.0, 260.0), "Game Server 1");
855 let srv2 = self.add_node(NetNodeKind::Server, Vec2::new(700.0, 260.0), "Game Server 2");
856 let srv3 = self.add_node(NetNodeKind::Server, Vec2::new(850.0, 260.0), "Game Server 3");
857 let voice = self.add_node(NetNodeKind::VoiceServer, Vec2::new(250.0, 260.0), "Voice Chat");
858 let metrics = self.add_node(NetNodeKind::Metrics, Vec2::new(400.0, 260.0), "Telemetry");
859 let cli1 = self.add_node(NetNodeKind::Client, Vec2::new(400.0, 400.0), "Client A");
860 let cli2 = self.add_node(NetNodeKind::Client, Vec2::new(550.0, 400.0), "Client B");
861 let cli3 = self.add_node(NetNodeKind::Client, Vec2::new(700.0, 400.0), "Client C");
862
863 self.add_edge(cli1, gw, NetworkProtocol::Quic);
864 self.add_edge(cli2, gw, NetworkProtocol::Quic);
865 self.add_edge(cli3, gw, NetworkProtocol::Quic);
866 self.add_edge(gw, auth, NetworkProtocol::Tcp);
867 self.add_edge(gw, match_, NetworkProtocol::Tcp);
868 self.add_edge(match_, lb, NetworkProtocol::Tcp);
869 self.add_edge(lb, srv1, NetworkProtocol::Udp);
870 self.add_edge(lb, srv2, NetworkProtocol::Udp);
871 self.add_edge(lb, srv3, NetworkProtocol::Udp);
872 self.add_edge(auth, db, NetworkProtocol::Tcp);
873 self.add_edge(srv1, db, NetworkProtocol::Tcp);
874 self.add_edge(srv1, metrics, NetworkProtocol::Udp);
875 self.add_edge(cli1, voice, NetworkProtocol::WebRtc);
876 self.add_edge(cli2, voice, NetworkProtocol::WebRtc);
877 }
878}
879
880#[derive(Debug, Clone, Copy, PartialEq)]
885pub enum NetworkEditorPanel {
886 Topology,
887 Replication,
888 BandwidthMonitor,
889 LagSimulation,
890 Lobby,
891 RpcBrowser,
892 Diagnostics,
893}
894
895impl NetworkEditorPanel {
896 pub fn label(&self) -> &'static str {
897 match self {
898 Self::Topology => "Topology",
899 Self::Replication => "Replication",
900 Self::BandwidthMonitor => "Bandwidth",
901 Self::LagSimulation => "Lag Sim",
902 Self::Lobby => "Lobby",
903 Self::RpcBrowser => "RPCs",
904 Self::Diagnostics => "Diagnostics",
905 }
906 }
907}
908
909#[derive(Debug, Clone, Copy, PartialEq)]
914pub enum NetworkEventKind {
915 PlayerConnected,
916 PlayerDisconnected,
917 PacketLost,
918 PacketReordered,
919 PacketDuplicated,
920 BandwidthThrottled,
921 HighLatency,
922 TimeOut,
923 Reconnected,
924 RpcDropped,
925 AuthFailed,
926 SessionCreated,
927 SessionDestroyed,
928 LevelLoad,
929}
930
931impl NetworkEventKind {
932 pub fn label(&self) -> &'static str {
933 match self {
934 Self::PlayerConnected => "Player Connected",
935 Self::PlayerDisconnected => "Player Disconnected",
936 Self::PacketLost => "Packet Lost",
937 Self::PacketReordered => "Packet Reordered",
938 Self::PacketDuplicated => "Packet Duplicated",
939 Self::BandwidthThrottled => "Bandwidth Throttled",
940 Self::HighLatency => "High Latency",
941 Self::TimeOut => "Time Out",
942 Self::Reconnected => "Reconnected",
943 Self::RpcDropped => "RPC Dropped",
944 Self::AuthFailed => "Auth Failed",
945 Self::SessionCreated => "Session Created",
946 Self::SessionDestroyed => "Session Destroyed",
947 Self::LevelLoad => "Level Load",
948 }
949 }
950
951 pub fn is_error(&self) -> bool {
952 matches!(self, Self::PacketLost | Self::TimeOut | Self::RpcDropped | Self::AuthFailed)
953 }
954
955 pub fn color(&self) -> Vec4 {
956 if self.is_error() {
957 Vec4::new(0.9, 0.3, 0.3, 1.0)
958 } else if matches!(self, Self::PlayerConnected | Self::SessionCreated | Self::Reconnected) {
959 Vec4::new(0.3, 0.9, 0.3, 1.0)
960 } else {
961 Vec4::new(0.7, 0.7, 0.7, 1.0)
962 }
963 }
964}
965
966#[derive(Debug, Clone)]
967pub struct NetworkDiagnosticEvent {
968 pub timestamp_ms: f64,
969 pub kind: NetworkEventKind,
970 pub player_id: Option<u64>,
971 pub description: String,
972 pub data: HashMap<String, String>,
973}
974
975#[derive(Debug)]
980pub struct NetworkEditor {
981 pub topology_graph: NetworkTopologyGraph,
982 pub session: NetworkSession,
983 pub actors: Vec<ReplicatedActor>,
984 pub bandwidth_monitor: BandwidthMonitor,
985 pub lag_sim: LagSimulation,
986 pub active_panel: NetworkEditorPanel,
987 pub selected_node: Option<u64>,
988 pub selected_actor: Option<usize>,
989 pub diagnostic_events: Vec<NetworkDiagnosticEvent>,
990 pub simulation_time_ms: f64,
991 pub network_paused: bool,
992 pub zoom: f32,
993 pub pan: Vec2,
994 pub search_query: String,
995 pub show_bandwidth_overlay: bool,
996 pub show_player_list: bool,
997}
998
999impl Default for NetworkEditor {
1000 fn default() -> Self {
1001 let mut bw_monitor = BandwidthMonitor::default();
1002 bw_monitor.generate_synthetic_samples(256);
1003
1004 let session = {
1005 let mut s = NetworkSession::new_lobby("Dev Session");
1006 s.players = vec![
1007 NetworkPlayer {
1008 player_id: 1,
1009 display_name: "HostPlayer".to_string(),
1010 ping_ms: 5,
1011 is_host: true,
1012 is_local: true,
1013 is_ready: true,
1014 team: Some(0),
1015 connection_quality: ConnectionQuality::Excellent,
1016 bytes_sent: 12_000,
1017 bytes_recv: 8_000,
1018 packet_loss_pct: 0.0,
1019 load_pct: 0.0,
1020 },
1021 NetworkPlayer {
1022 player_id: 2,
1023 display_name: "Player2".to_string(),
1024 ping_ms: 48,
1025 is_host: false,
1026 is_local: false,
1027 is_ready: true,
1028 team: Some(0),
1029 connection_quality: ConnectionQuality::Good,
1030 bytes_sent: 11_000,
1031 bytes_recv: 9_000,
1032 packet_loss_pct: 0.1,
1033 load_pct: 0.0,
1034 },
1035 NetworkPlayer {
1036 player_id: 3,
1037 display_name: "Player3".to_string(),
1038 ping_ms: 142,
1039 is_host: false,
1040 is_local: false,
1041 is_ready: false,
1042 team: Some(1),
1043 connection_quality: ConnectionQuality::Fair,
1044 bytes_sent: 9_000,
1045 bytes_recv: 7_500,
1046 packet_loss_pct: 0.8,
1047 load_pct: 0.0,
1048 },
1049 ];
1050 s
1051 };
1052
1053 let actors = vec![
1054 ReplicatedActor::player_actor(),
1055 {
1056 let mut a = ReplicatedActor::new("Projectile");
1057 a.properties.push({
1058 let mut p = ReplicatedProperty::new("position", ReplicatedPropertyType::Vector3);
1059 p.update_rate_hz = 60.0;
1060 p
1061 });
1062 a.properties.push({
1063 let mut p = ReplicatedProperty::new("velocity", ReplicatedPropertyType::Vector3);
1064 p.update_rate_hz = 60.0;
1065 p
1066 });
1067 a.always_relevant = true;
1068 a
1069 },
1070 ];
1071
1072 Self {
1073 topology_graph: NetworkTopologyGraph::default(),
1074 session,
1075 actors,
1076 bandwidth_monitor: bw_monitor,
1077 lag_sim: LagSimulation::default(),
1078 active_panel: NetworkEditorPanel::Topology,
1079 selected_node: None,
1080 selected_actor: None,
1081 diagnostic_events: Vec::new(),
1082 simulation_time_ms: 0.0,
1083 network_paused: false,
1084 zoom: 1.0,
1085 pan: Vec2::ZERO,
1086 search_query: String::new(),
1087 show_bandwidth_overlay: true,
1088 show_player_list: true,
1089 }
1090 }
1091}
1092
1093impl NetworkEditor {
1094 pub fn simulate_tick(&mut self, dt_ms: f64) {
1095 if self.network_paused { return; }
1096 self.simulation_time_ms += dt_ms;
1097 let mut rng: u64 = self.simulation_time_ms as u64 * 2654435761;
1099 rng ^= rng >> 33;
1100 rng = rng.wrapping_mul(0xFF51AFD7ED558CCD);
1101 let noise = ((rng >> 33) as f32) / (u32::MAX as f32) * 2.0 - 1.0;
1102 self.bandwidth_monitor.push_sample(BandwidthSample {
1103 timestamp_ms: self.simulation_time_ms,
1104 bytes_sent: (1024 + (noise * 256.0) as i64).max(0) as u64,
1105 bytes_recv: (512 + (noise * 128.0) as i64).max(0) as u64,
1106 packets_sent: 8,
1107 packets_recv: 8,
1108 packets_lost: if noise > 0.95 { 1 } else { 0 },
1109 rtt_ms: self.lag_sim.rtt_ms() + noise * self.lag_sim.jitter_ms,
1110 });
1111 }
1112
1113 pub fn apply_lag_profile(&mut self, profile: LagProfile) {
1114 profile.apply_to_sim(&mut self.lag_sim);
1115 }
1116
1117 pub fn total_replicated_bandwidth_bps(&self) -> f32 {
1118 self.actors.iter().map(|a| a.total_bandwidth_bps()).sum()
1119 }
1120
1121 pub fn generate_network_report(&self) -> String {
1122 let mut lines = Vec::new();
1123 lines.push("=== Network Report ===".to_string());
1124 lines.push(format!("Topology: {}", self.session.topology.label()));
1125 lines.push(format!("State: {}", self.session.state.label()));
1126 lines.push(format!("Players: {}/{}", self.session.player_count(), self.session.max_players));
1127 lines.push(format!("Avg Ping: {:.1} ms", self.session.average_ping()));
1128 lines.push(format!("Bandwidth Send: {:.1} kB/s", self.bandwidth_monitor.current_send_kbps()));
1129 lines.push(format!("Bandwidth Recv: {:.1} kB/s", self.bandwidth_monitor.current_recv_kbps()));
1130 lines.push(format!("Packet Loss: {:.2}%", self.bandwidth_monitor.packet_loss_pct()));
1131 lines.push(format!("Lag Sim: {} (latency: {:.0} ms, loss: {:.1}%)",
1132 if self.lag_sim.enabled { "ON" } else { "OFF" },
1133 self.lag_sim.latency_ms,
1134 self.lag_sim.packet_loss_pct,
1135 ));
1136 lines.push(format!("Replicated Actors: {}", self.actors.len()));
1137 lines.push(format!("Total Replication Bandwidth: {:.0} b/s", self.total_replicated_bandwidth_bps()));
1138 lines.join("\n")
1139 }
1140}