main.rs (7570B)
1 use std::net::{Ipv4Addr, SocketAddr}; 2 3 use bevy::{ 4 log::{Level, LogPlugin}, 5 prelude::*, 6 scene::ron, 7 window::PresentMode, 8 }; 9 use bevy_ecs_ldtk::prelude::*; 10 use clap::Parser; 11 use iyes_perf_ui::{PerfUiCompleteBundle, PerfUiPlugin}; 12 use lightyear::{ 13 connection::netcode::ClientId, 14 shared::log::add_log_layer, 15 transport::{io::TransportConfig, LOCAL_SOCKET}, 16 }; 17 use networking::{ 18 client::ClientPluginGroup, server::ServerPluginGroup, ClientSettings, ClientTransports, 19 ServerTransports, Settings, 20 }; 21 use wall::WallBundle; 22 23 mod networking; 24 mod player; 25 mod wall; 26 27 #[derive(Parser, PartialEq, Debug)] 28 enum Cli { 29 #[cfg(not(target_family = "wasm"))] 30 /// The program will act both as a server and as a client 31 ListenServer, 32 #[cfg(not(target_family = "wasm"))] 33 /// Dedicated server 34 Server, 35 /// The program will act as a client 36 Client, 37 } 38 39 fn main() { 40 cfg_if::cfg_if! { 41 if #[cfg(target_family = "wasm")] { 42 let client_id = rand::random::<u64>(); 43 let cli = Cli::Client { 44 client_id: Some(client_id) 45 }; 46 } else { 47 let cli = Cli::parse(); 48 } 49 } 50 let settings_str = include_str!("../assets/settings.ron"); 51 let settings = ron::de::from_str::<Settings>(settings_str).unwrap(); 52 run(settings, cli); 53 } 54 55 fn run(settings: Settings, cli: Cli) { 56 match cli { 57 #[cfg(not(target_family = "wasm"))] 58 Cli::ListenServer => { 59 // create client app 60 let (from_server_send, from_server_recv) = crossbeam_channel::unbounded(); 61 let (to_server_send, to_server_recv) = crossbeam_channel::unbounded(); 62 let transport_config = TransportConfig::LocalChannel { 63 recv: from_server_recv, 64 send: to_server_send, 65 }; 66 // when communicating via channels, we need to use the address `LOCAL_SOCKET` for the server 67 let client_id = rand::random::<u64>(); 68 let mut client_app = 69 client_app(settings.clone(), LOCAL_SOCKET, client_id, transport_config); 70 71 // create server app 72 let extra_transport_configs = vec![TransportConfig::Channels { 73 // even if we communicate via channels, we need to provide a socket address for the client 74 channels: vec![(LOCAL_SOCKET, to_server_recv, from_server_send)], 75 }]; 76 let mut server_app = server_app(settings, extra_transport_configs); 77 78 // run both the client and server apps 79 std::thread::spawn(move || server_app.run()); 80 client_app.run(); 81 } 82 #[cfg(not(target_family = "wasm"))] 83 Cli::Server => { 84 let mut app = server_app(settings, vec![]); 85 app.run(); 86 } 87 Cli::Client => { 88 let server_addr = SocketAddr::new( 89 settings.client.server_addr.into(), 90 settings.client.server_port, 91 ); 92 let transport_config = get_client_transport_config(settings.client.clone()); 93 let client_id = rand::random::<u64>(); 94 let mut app = client_app(settings, server_addr, client_id, transport_config); 95 app.run(); 96 } 97 } 98 } 99 100 /// Build the client app 101 fn client_app( 102 settings: Settings, 103 server_addr: SocketAddr, 104 client_id: ClientId, 105 transport_config: TransportConfig, 106 ) -> App { 107 let mut app = App::new(); 108 // NOTE: create the default plugins first so that the async task pools are initialized 109 // use the default bevy logger for now 110 // (the lightyear logger doesn't handle wasm) 111 app.add_plugins( 112 DefaultPlugins 113 .build() 114 .set(LogPlugin { 115 level: Level::INFO, 116 filter: "wgpu=error,bevy_render=info".to_string(), 117 update_subscriber: Some(add_log_layer), 118 }) 119 .set(WindowPlugin { 120 primary_window: Some(Window { 121 title: "Matrix RPG".into(), 122 present_mode: PresentMode::AutoVsync, 123 prevent_default_event_handling: false, 124 ..default() 125 }), 126 ..default() 127 }) 128 .set(ImagePlugin::default_nearest()), 129 ); 130 if settings.client.inspector { 131 app.add_plugins(PerfUiPlugin); 132 } 133 app.add_plugins(LdtkPlugin) 134 .insert_resource(LevelSelection::index(0)) 135 .insert_resource(LdtkSettings { 136 set_clear_color: SetClearColor::FromLevelBackground, 137 ..Default::default() 138 }) 139 .add_systems(Startup, move |mut commands: Commands| { 140 if settings.client.inspector { 141 commands.spawn(PerfUiCompleteBundle::default()); 142 } 143 }) 144 .register_ldtk_int_cell::<WallBundle>(1) 145 .add_plugins(player::PlayerPlugin); 146 let client_plugin_group = ClientPluginGroup::new( 147 // use the cli-provided client id if it exists, otherwise use the settings client id 148 client_id, 149 server_addr, 150 transport_config, 151 settings.shared, 152 ); 153 app.add_plugins(client_plugin_group.build()); 154 app 155 } 156 157 /// Build the server app 158 fn server_app(settings: Settings, extra_transport_configs: Vec<TransportConfig>) -> App { 159 let mut app = App::new(); 160 if !settings.server.headless { 161 app.add_plugins(DefaultPlugins.build().disable::<LogPlugin>()); 162 } else { 163 app.add_plugins(MinimalPlugins); 164 } 165 app.add_plugins(LogPlugin { 166 level: Level::INFO, 167 filter: "wgpu=error,bevy_render=info".to_string(), 168 update_subscriber: Some(add_log_layer), 169 }); 170 171 if settings.server.inspector { 172 app.add_plugins(PerfUiPlugin); 173 } 174 app.add_systems(Startup, move |mut commands: Commands| { 175 if settings.client.inspector { 176 commands.spawn(PerfUiCompleteBundle::default()); 177 } 178 }); 179 let mut transport_configs = get_server_transport_configs(settings.server.transport); 180 transport_configs.extend(extra_transport_configs); 181 let server_plugin_group = ServerPluginGroup::new(transport_configs, settings.shared); 182 app.add_plugins(server_plugin_group.build()); 183 app 184 } 185 186 /// Parse the server transport settings into a list of `TransportConfig` that are used to configure the lightyear server 187 fn get_server_transport_configs(settings: Vec<ServerTransports>) -> Vec<TransportConfig> { 188 settings 189 .iter() 190 .map(|t| match t { 191 ServerTransports::Udp { local_port } => TransportConfig::UdpSocket(SocketAddr::new( 192 Ipv4Addr::UNSPECIFIED.into(), 193 *local_port, 194 )), 195 ServerTransports::WebSocket { local_port } => TransportConfig::WebSocketServer { 196 server_addr: SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), *local_port), 197 }, 198 }) 199 .collect() 200 } 201 202 /// Parse the client transport settings into a `TransportConfig` that is used to configure the lightyear client 203 fn get_client_transport_config(settings: ClientSettings) -> TransportConfig { 204 let server_addr = SocketAddr::new(settings.server_addr.into(), settings.server_port); 205 let client_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), settings.client_port); 206 match settings.transport { 207 #[cfg(not(target_family = "wasm"))] 208 ClientTransports::Udp => TransportConfig::UdpSocket(client_addr), 209 ClientTransports::WebSocket => TransportConfig::WebSocketClient { server_addr }, 210 } 211 }