diff options
author | metamuffin <yvchraiqi@protonmail.com> | 2022-06-09 09:46:00 +0200 |
---|---|---|
committer | metamuffin <yvchraiqi@protonmail.com> | 2022-06-09 09:46:00 +0200 |
commit | 7a0d09e5cd0075e2a0d3db4505d7ec77dff35ae0 (patch) | |
tree | 5586745b7a9b871b31512cba9f964dabda4651f0 /renderer/src | |
download | twclient-7a0d09e5cd0075e2a0d3db4505d7ec77dff35ae0.tar twclient-7a0d09e5cd0075e2a0d3db4505d7ec77dff35ae0.tar.bz2 twclient-7a0d09e5cd0075e2a0d3db4505d7ec77dff35ae0.tar.zst |
(reset git)
Diffstat (limited to 'renderer/src')
-rw-r--r-- | renderer/src/main.rs | 254 | ||||
-rw-r--r-- | renderer/src/map.rs | 163 |
2 files changed, 417 insertions, 0 deletions
diff --git a/renderer/src/main.rs b/renderer/src/main.rs new file mode 100644 index 0000000..d763336 --- /dev/null +++ b/renderer/src/main.rs @@ -0,0 +1,254 @@ +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<PossiblyCurrent, Window>, + } + + 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<PossiblyCurrent, Window>, + 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() +} diff --git a/renderer/src/map.rs b/renderer/src/map.rs new file mode 100644 index 0000000..0ea3a6b --- /dev/null +++ b/renderer/src/map.rs @@ -0,0 +1,163 @@ +use std::collections::HashMap; + +use log::{info, warn}; +use skia_safe::{ + canvas::SrcRectConstraint, Canvas, Color4f, ColorSpace, ISize, Image, Paint, Rect, +}; +use twclient::world::{ + map::{format, TILE_NUM}, + World, +}; + +pub struct MapRenderer { + tileset: HashMap<Option<usize>, Image>, +} + +impl MapRenderer { + pub fn new() -> Self { + Self { + tileset: HashMap::new(), + } + } + + pub fn tick(&mut self) {} + pub fn map_changed(&mut self, world: &World) { + self.tileset.clear(); + for (key, t) in &world.map.tilesets { + let mut bytes: Vec<u8> = Vec::with_capacity(t.dim().0 * t.dim().1 * 4); + info!("loading tileset: {:?} => {:?}", key, t.dim()); + for ((_x, _y), c) in t.indexed_iter() { + bytes.push(c.red); + bytes.push(c.green); + bytes.push(c.blue); + bytes.push(c.alpha); + } + let d = skia_safe::Data::new_copy(&bytes); + let v = skia_safe::Image::from_raster_data( + &skia_safe::ImageInfo::new( + ISize::new(t.dim().0 as i32, t.dim().1 as i32), + skia_safe::ColorType::RGBA8888, + skia_safe::AlphaType::Opaque, + None, + ), + d, + t.dim().0 * 4, + ) + .unwrap(); + self.tileset.insert(*key, v); + } + } + + pub fn draw(&mut self, world: &World, canvas: &mut Canvas) { + let mut grid_paint = Paint::new( + Color4f { + a: 1.0, + r: 1.0, + g: 1.0, + b: 1.0, + }, + &ColorSpace::new_srgb(), + ); + grid_paint.set_style(skia_safe::PaintStyle::Stroke); + grid_paint.set_anti_alias(true); + + let center = world + .local_tee() + .map(|t| (t.x / 16, t.y / 16)) + .unwrap_or((0, 0)); + + let tile_rect = Rect { + top: center.1 as f32 * 16.0, + left: center.0 as f32 * 16.0, + bottom: center.1 as f32 * 16.0 + 16.0, + right: center.0 as f32 * 16.0 + 16.0, + }; + canvas.draw_rect(tile_rect, &grid_paint); + + for l in &world.map.layers { + let tileset = match world.map.tilesets.get(&l.image) { + Some(t) => t, + None => { + warn!("missing tileset for {:?}, skipping layer", l.image); + continue; + } + }; + if tileset.dim() == (1, 1) { + continue; + } + + for layer_y in (center.1 - 30)..(center.1 + 30) { + for layer_x in (center.0 - 30)..(center.0 + 30) { + let layer_x = layer_x.try_into().unwrap_or(0); + let layer_y = layer_y.try_into().unwrap_or(0); + + if layer_x >= l.tiles.dim().1 || layer_y >= l.tiles.dim().0 { + continue; + } + let tile = l.tiles[(layer_y, layer_x)]; + + let _rotate = tile.flags & format::TILEFLAG_ROTATE != 0; + let _vflip = tile.flags & format::TILEFLAG_VFLIP != 0; + let _hflip = tile.flags & format::TILEFLAG_HFLIP != 0; + let tile_x = tile.index as u32 % TILE_NUM; + let tile_y = tile.index as u32 / TILE_NUM; + + if tile_x == 0 && tile_y == 0 { + continue; + } + + let tile_rect = Rect { + top: layer_y as f32 * 16.0, + left: layer_x as f32 * 16.0, + bottom: layer_y as f32 * 16.0 + 16.0, + right: layer_x as f32 * 16.0 + 16.0, + }; + canvas.draw_rect(tile_rect, &grid_paint); + + const TL: u32 = 16; + canvas.draw_image_rect( + self.tileset.get(&l.image).unwrap(), + Some(( + &Rect { + top: (tile_y * TL) as f32, + left: (tile_x * TL) as f32, + bottom: ((tile_y + 1) * TL) as f32, + right: ((tile_x + 1) * TL) as f32, + }, + SrcRectConstraint::Strict, + )), + tile_rect, + &Paint::default(), + ); + // for iy in 0..TL { + // for ix in 0..TL { + // let (x, y) = ((layer_x as u32 * TL + iy), (layer_y as u32 * TL + ix)); + // let p_tile = + // tileset[((tile_y * TL + iy) as usize, (tile_x * TL + ix) as usize)]; + // let mut p = Paint::new( + // Color4f { + // a: 0.0, + // b: 0.0, + // g: 0.0, + // r: 0.0, + // }, + // &ColorSpace::new_srgb(), + // ); + // p.set_argb(p_tile.alpha, p_tile.red, p_tile.green, p_tile.green); + // p.set_style(PaintStyle::Fill); + // canvas.draw_rect( + // Rect { + // left: x as f32, + // top: y as f32, + // right: x as f32 + 1.0, + // bottom: y as f32 + 1.0, + // }, + // &p, + // ); + // } + // } + } + } + } + } +} |