pub mod map; pub mod tee; use glutin::{ event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::{Window, WindowBuilder}, ContextWrapper, GlProfile, PossiblyCurrent, }; use log::{error, info, warn}; use map::MapRenderer; use signal_hook::{ consts::{SIGINT, SIGTERM}, iterator::Signals, }; use skia_safe::{ gpu::{gl::FramebufferInfo, BackendRenderTarget, SurfaceOrigin}, Canvas, Color, ColorType, Surface, }; use std::{ collections::HashSet, convert::TryInto, net::IpAddr, process::exit, str::FromStr, sync::atomic::Ordering, thread, time::Duration, }; use tee::TeeRenderer; use twclient::{ client::{Client, ClientConfig, ClientInterface, ClientMesgIn, PlayerInput}, world::World, SHOULD_EXIT, }; fn main() { env_logger::init(); let event_loop = EventLoop::new(); let wb = WindowBuilder::new().with_title("teeworlds"); let cb = glutin::ContextBuilder::new() .with_depth_buffer(0) .with_stencil_buffer(8) .with_pixel_format(24, 8) .with_gl_profile(GlProfile::Core); // TODO // #[cfg(not(feature = "wayland"))] // let cb = cb.with_double_buffer(Some(true)); let windowed_context = cb.build_windowed(wb, &event_loop).unwrap(); let windowed_context = unsafe { windowed_context.make_current().unwrap() }; gl::load_with(|s| windowed_context.get_proc_address(s)); let mut gr_context = skia_safe::gpu::DirectContext::new_gl(None, None).unwrap(); let fb_info = { use gl::types::GLint; let mut fboid: GLint = 0; unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) }; FramebufferInfo { fboid: fboid.try_into().unwrap(), format: skia_safe::gpu::gl::Format::RGBA8.into(), } }; windowed_context .window() .set_inner_size(glutin::dpi::Size::new(glutin::dpi::LogicalSize::new( 1024.0, 1024.0, ))); let surface = create_surface(&windowed_context, &fb_info, &mut gr_context); struct Env { surface: Surface, gr_context: skia_safe::gpu::DirectContext, windowed_context: ContextWrapper, } let mut env = Env { surface, gr_context, windowed_context, }; let mut args = std::env::args().skip(1); let ip = IpAddr::from_str(args.next().unwrap().as_str()).unwrap(); let port = u16::from_str(args.next().unwrap().as_str()).unwrap(); let mut evloop = Client::new_evloop(); let (client, client_interface) = Client::new( &mut evloop, ip, port, ClientConfig { nick: "metamuffin".to_string(), clan: "rustacean".to_string(), timeout: "sdfhaiusdfhus".to_string(), }, ); let mut network_thread = Some(std::thread::spawn(move || client.run(evloop))); let mut signals = Signals::new(&[SIGTERM, SIGINT]).unwrap(); info!("setting up signal handlers"); thread::spawn(move || { for sig in signals.forever() { warn!("received signal {:?}", sig); SHOULD_EXIT.store(true, Ordering::Relaxed); thread::sleep(Duration::from_secs(3)); error!("exit timeout!"); exit(1); } }); let mut renderer = Renderer { client_interface, tee_renderer: TeeRenderer::new(), map_renderer: MapRenderer::new(), world: World::new(), input: PlayerInput::default(), }; let mut keys_down = HashSet::::new(); event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; #[allow(deprecated)] match event { Event::LoopDestroyed => {} Event::RedrawEventsCleared => { if SHOULD_EXIT.load(Ordering::Relaxed) { warn!("waiting for network thread to finish"); network_thread.take().unwrap().join().unwrap(); warn!("exiting renderer"); exit(0); } renderer.tick(); env.windowed_context.window().request_redraw(); } Event::WindowEvent { event, .. } => match event { WindowEvent::Resized(physical_size) => { env.surface = create_surface(&env.windowed_context, &fb_info, &mut env.gr_context); env.windowed_context.resize(physical_size) } WindowEvent::CloseRequested => { warn!("renderer event loop stopped, telling the client to exit too"); SHOULD_EXIT.store(true, Ordering::Relaxed); *control_flow = ControlFlow::Exit } WindowEvent::KeyboardInput { input: KeyboardInput { virtual_keycode, state, .. }, .. } => { if let Some(k) = virtual_keycode { let sk = if state == ElementState::Pressed { 1 } else { -1 }; let sa = if state == ElementState::Pressed { 1 } else { 0 }; let repeat = match state { ElementState::Pressed => !keys_down.insert(k), ElementState::Released => !keys_down.remove(&k), }; if !repeat { match k { VirtualKeyCode::A => { renderer.input.direction += sk * -1; } VirtualKeyCode::D => { renderer.input.direction += sk * 1; } VirtualKeyCode::Space => { renderer.input.jump = sa; } _ => (), } renderer .client_interface .send .send(ClientMesgIn::Input(renderer.input)) .unwrap(); } } } _ => (), }, Event::RedrawRequested(_) => { { let dims = (env.surface.width() as f32, env.surface.height() as f32); let canvas = env.surface.canvas(); renderer.draw(canvas, dims) } env.surface.canvas().flush(); env.windowed_context.swap_buffers().unwrap(); } _ => (), } }); } pub struct Renderer { client_interface: ClientInterface, map_renderer: MapRenderer, tee_renderer: TeeRenderer, world: World, input: PlayerInput, } impl Renderer { pub fn tick(&mut self) { for m in self.client_interface.receive.try_iter() { self.world.update(&m); match m { twclient::client::ClientMesgOut::MapChange { .. } => { self.map_renderer.map_changed(&self.world) } _ => (), } } } pub fn draw(&mut self, canvas: &mut Canvas, dims: (f32, f32)) { canvas.clear(Color::TRANSPARENT); let center = self .world .tees .local() .map(|t| (t.x, t.y)) .unwrap_or((0, 0)); canvas.save(); canvas.translate((dims.0 / 2.0, dims.1 / 2.0)); canvas.translate((-center.0 as f32, -center.1 as f32)); self.map_renderer.draw(&self.world, canvas); self.tee_renderer.draw(&self.world, canvas); canvas.restore(); } } fn create_surface( windowed_context: &ContextWrapper, fb_info: &FramebufferInfo, gr_context: &mut skia_safe::gpu::DirectContext, ) -> skia_safe::Surface { let pixel_format = windowed_context.get_pixel_format(); let size = windowed_context.window().inner_size(); let backend_render_target = BackendRenderTarget::new_gl( ( size.width.try_into().unwrap(), size.height.try_into().unwrap(), ), pixel_format.multisampling.map(|s| s.try_into().unwrap()), pixel_format.stencil_bits.try_into().unwrap(), *fb_info, ); Surface::from_backend_render_target( gr_context, &backend_render_target, SurfaceOrigin::BottomLeft, ColorType::RGBA8888, None, None, ) .unwrap() }