pub mod map; use glutin::{ event::{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, Color4f, ColorSpace, ColorType, Paint, Point, Surface, }; use std::{ convert::TryInto, net::IpAddr, process::exit, str::FromStr, sync::atomic::Ordering, thread, time::Duration, }; use twclient::{ client::{Client, ClientConfig, ClientInterface}, 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, map_renderer: MapRenderer::new(), world: World::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 networt 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, modifiers, .. }, .. } => { if modifiers.logo() { if let Some(VirtualKeyCode::Q) = virtual_keycode { *control_flow = ControlFlow::Exit; } } env.windowed_context.window().request_redraw(); } _ => (), }, 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, world: World, } 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.local_tee().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); let tee_paint = Paint::new( Color4f { a: 1.0, r: 1.0, g: 0.0, b: 1.0, }, &ColorSpace::new_srgb(), ); for t in self.world.tees.values() { canvas.draw_circle( Point { x: t.x as f32, y: t.y as f32, }, 16.0, &tee_paint, ); } 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() }