From b2145131ccde0a33b9840ac04c8b7d79e733ae12 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Wed, 8 Jan 2025 11:14:01 +0100 Subject: reset --- COPYING | 5 +- Cargo.toml | 3 +- client/Cargo.toml | 46 +--- client/src/client/helper.rs | 87 -------- client/src/client/mod.rs | 431 ------------------------------------- client/src/lib.rs | 21 -- client/src/main.rs | 40 +--- client/src/world/helper.rs | 19 -- client/src/world/map.rs | 158 -------------- client/src/world/mod.rs | 34 --- client/src/world/tee.rs | 167 --------------- readme.md | 8 +- renderer/Cargo.toml | 18 -- renderer/src/main.rs | 278 ------------------------ renderer/src/map.rs | 161 -------------- renderer/src/tee.rs | 206 ------------------ snapshot/Cargo.toml | 27 --- snapshot/src/format.rs | 85 -------- snapshot/src/lib.rs | 36 ---- snapshot/src/manager.rs | 140 ------------ snapshot/src/receiver.rs | 245 --------------------- snapshot/src/snap.rs | 511 -------------------------------------------- snapshot/src/storage.rs | 176 --------------- 23 files changed, 10 insertions(+), 2892 deletions(-) delete mode 100644 client/src/client/helper.rs delete mode 100644 client/src/client/mod.rs delete mode 100644 client/src/lib.rs delete mode 100644 client/src/world/helper.rs delete mode 100644 client/src/world/map.rs delete mode 100644 client/src/world/mod.rs delete mode 100644 client/src/world/tee.rs delete mode 100644 renderer/Cargo.toml delete mode 100644 renderer/src/main.rs delete mode 100644 renderer/src/map.rs delete mode 100644 renderer/src/tee.rs delete mode 100644 snapshot/Cargo.toml delete mode 100644 snapshot/src/format.rs delete mode 100644 snapshot/src/lib.rs delete mode 100644 snapshot/src/manager.rs delete mode 100644 snapshot/src/receiver.rs delete mode 100644 snapshot/src/snap.rs delete mode 100644 snapshot/src/storage.rs diff --git a/COPYING b/COPYING index 33e5680..3b6c7f9 100644 --- a/COPYING +++ b/COPYING @@ -1,6 +1,5 @@ -twclient - metamuffin's teeworlds client in rust -Copyright (C) 2022 metamuffin -Based on MIT licenced code of libtw2 by heinrich5991 +twclient - teeworlds client in rust +Copyright (C) 2025 metamuffin This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as diff --git a/Cargo.toml b/Cargo.toml index 19cae09..0e1d230 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,3 @@ [workspace] -members = ["client","snapshot","renderer"] \ No newline at end of file +members = ["client"] +resolver = "3" diff --git a/client/Cargo.toml b/client/Cargo.toml index 1e58906..09c50c3 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -1,48 +1,6 @@ [package] -name = "twclient" +name = "client" version = "0.1.0" -edition = "2021" -authors = [ - "metamuffin ", - "heinrich5991 ", -] -license = "AGPL-3.0-only" +edition = "2024" [dependencies] -socket = { path = "../../libtw2/socket" } -datafile = { path = "../../libtw2/datafile" } -map = { path = "../../libtw2/map" } -net = { path = "../../libtw2/net" } -common = { path = "../../libtw2/common" } -event_loop = { path = "../../libtw2/event_loop" } -gamenet_teeworlds_0_5 = { path = "../../libtw2/gamenet/teeworlds-0.5", optional = true } -gamenet_teeworlds_0_6 = { path = "../../libtw2/gamenet/teeworlds-0.6", optional = true } -gamenet_teeworlds_0_7 = { path = "../../libtw2/gamenet/teeworlds-0.7", optional = true } -gamenet_ddnet = { path = "../../libtw2/gamenet/ddnet", optional = true } -logger = { path = "../../libtw2/logger" } -packer = { path = "../../libtw2/packer" } -# snapshot = { path = "../../libtw2/snapshot" } -snapshot = { path = "../snapshot" } - -ndarray = "0.15.4" -arrayvec = "0.5.2" -clap = "2.31.2" -hexdump = "0.1.1" -itertools = ">=0.3.0,<0.5.0" -log = "0.3.1" -rand = "0.8.3" -tempfile = "2.0.0" -warn = ">=0.1.1,<0.3.0" -env_logger = "0.9.0" -signal-hook = "0.3.14" -lazy_static = "1.4.0" -anyhow = "1.0.57" -crossbeam-channel = "0.5.4" - - -[features] -default = ["gamenet_ddnet_0_6"] -gamenet_0_5 = ["dep:gamenet_teeworlds_0_5", "snapshot/gamenet_teeworlds_0_5"] -gamenet_0_6 = ["dep:gamenet_teeworlds_0_6", "snapshot/gamenet_teeworlds_0_6"] -gamenet_0_7 = ["dep:gamenet_teeworlds_0_7", "snapshot/gamenet_teeworlds_0_7"] -gamenet_ddnet_0_6 = ["dep:gamenet_ddnet", "snapshot/gamenet_ddnet_0_6"] diff --git a/client/src/client/helper.rs b/client/src/client/helper.rs deleted file mode 100644 index af19301..0000000 --- a/client/src/client/helper.rs +++ /dev/null @@ -1,87 +0,0 @@ -use arrayvec::ArrayVec; -use event_loop::{Chunk, Loop, PeerId}; -use gamenet::msg::{Game, System}; -use hexdump::hexdump_iter; -use itertools::Itertools; -use log::LogLevel; -use log::{log, log_enabled, warn}; -use packer::with_packer; -use snapshot::format::Item as SnapItem; -use std::{fmt, path::PathBuf}; - -pub trait LoopExt: Loop { - fn send_system<'a, S: Into>>(&mut self, pid: PeerId, msg: S) { - fn inner(msg: System, pid: PeerId, evloop: &mut L) { - let mut buf: ArrayVec<[u8; 2048]> = ArrayVec::new(); - with_packer(&mut buf, |p| msg.encode(p).unwrap()); - evloop.send(Chunk { - pid, - vital: true, - data: &buf, - }) - } - inner(msg.into(), pid, self) - } - fn send_game<'a, G: Into>>(&mut self, pid: PeerId, msg: G) { - fn inner(msg: Game, pid: PeerId, evloop: &mut L) { - let mut buf: ArrayVec<[u8; 2048]> = ArrayVec::new(); - with_packer(&mut buf, |p| msg.encode(p).unwrap()); - evloop.send(Chunk { - pid, - vital: true, - data: &buf, - }) - } - inner(msg.into(), pid, self) - } -} -impl LoopExt for L {} - -pub fn need_file(crc: i32, name: &str) -> bool { - let mut path = PathBuf::new(); - path.push("/tmp/maps"); - path.push(format!("{}_{:08x}.map", name, crc)); - !path.exists() -} - -pub fn hexdump(level: LogLevel, data: &[u8]) { - if log_enabled!(level) { - hexdump_iter(data).foreach(|s| log!(level, "{}", s)); - } -} -pub struct Warn<'a>(pub &'a [u8]); - -impl<'a, W: fmt::Debug> warn::Warn for Warn<'a> { - fn warn(&mut self, w: W) { - warn!("{:?}", w); - hexdump(LogLevel::Warn, self.0); - } -} - -#[derive(Debug)] -pub struct WarnSnap<'a>(pub SnapItem<'a>); - -impl<'a, W: fmt::Debug> warn::Warn for WarnSnap<'a> { - fn warn(&mut self, w: W) { - warn!("{:?} for {:?}", w, self.0); - } -} - -pub fn check_dummy_map(name: &[u8], crc: u32, size: i32) -> bool { - if name != b"dummy" { - return false; - } - match (crc, size) { - (0xbeae0b9f, 549) => {} - (0x6c760ac4, 306) => {} - _ => warn!("unknown dummy map, crc={}, size={}", crc, size), - } - true -} - -pub fn get_map_path(name: &str, crc: i32) -> PathBuf { - let mut path = PathBuf::new(); - path.push("/tmp/maps"); - path.push(format!("{}_{:08x}.map", name, crc)); - path -} diff --git a/client/src/client/mod.rs b/client/src/client/mod.rs deleted file mode 100644 index c5e7b9e..0000000 --- a/client/src/client/mod.rs +++ /dev/null @@ -1,431 +0,0 @@ -pub mod helper; - -use self::helper::get_map_path; -use super::gamenet::{ - enums::{Team, VERSION}, - msg::{ - self, - game::{ClSetTeam, ClStartInfo}, - system::{EnterGame, Info, Input, MapChange, MapData}, - system::{Ready, RequestMapData}, - Game, System, SystemOrGame, - }, - snap_obj::obj_size, - SnapObj, -}; -use crate::client::helper::{check_dummy_map, hexdump, WarnSnap}; -use crate::{ - client::helper::{need_file, LoopExt, Warn}, - SHOULD_EXIT, -}; -use common::{num::Cast, pretty}; -use crossbeam_channel::{Receiver, Sender}; -use event_loop::{Addr, Application, Chunk, ConnlessChunk, Loop, PeerId, SocketLoop, Timeout}; -use log::LogLevel; -use log::{debug, error, info, log, warn}; -use packer::{IntUnpacker, Unpacker}; -use std::{ - borrow::{Borrow, Cow}, - collections::HashSet, - fs, - io::{self, Write}, - mem, - net::IpAddr, - sync::atomic::Ordering, - u32, -}; -use tempfile::{NamedTempFile, NamedTempFileOptions}; -use warn::Log; - -pub use super::gamenet::snap_obj::PlayerInput; - -pub struct ClientConfig { - pub nick: String, - pub clan: String, - pub timeout: String, -} - -pub struct Client { - pid: PeerId, - config: ClientConfig, - - current_votes: HashSet>, - snaps: snapshot::Manager, - num_snaps_since_reset: u64, - dummy_map: bool, - state: ClientState, - download: Option, - - input: PlayerInput, - - interface_receive: Receiver, - interface_send: Sender, -} - -pub struct ClientInterface { - pub send: Sender, - pub receive: Receiver, -} - -pub enum ClientMesgIn { - Input(PlayerInput), -} -pub enum ClientMesgOut { - MapChange { name: String, crc: i32 }, - Snaps(Vec<(u16, SnapObj)>), -} - -#[derive(Clone, Copy, Debug)] -enum ClientState { - Connection, - MapChange, - MapData(i32, i32), - ConReady, - ReadyToEnter, -} - -impl Default for ClientState { - fn default() -> ClientState { - ClientState::Connection - } -} - -impl Client { - pub fn new_evloop() -> SocketLoop { - SocketLoop::client() - } - - pub fn new( - evloop: &mut SocketLoop, - ip: IpAddr, - port: u16, - config: ClientConfig, - ) -> (Self, ClientInterface) { - fs::create_dir_all("/tmp/maps").unwrap(); - fs::create_dir_all("/tmp/downloading").unwrap(); - let (a, b) = crossbeam_channel::unbounded(); - let (c, d) = crossbeam_channel::unbounded(); - ( - Client { - pid: evloop.connect(Addr { ip, port }), - config, - current_votes: HashSet::new(), - snaps: snapshot::Manager::new(), - num_snaps_since_reset: 0, - dummy_map: false, - state: ClientState::Connection, - download: None, - interface_receive: b, - interface_send: c, - input: PlayerInput::default(), - }, - ClientInterface { - receive: d, - send: a, - }, - ) - } - pub fn run(self, evloop: SocketLoop) { - evloop.run(self) - } -} - -impl<'a, L: Loop> Application for Client { - fn needs_tick(&mut self) -> Timeout { - Timeout::inactive() - } - fn on_tick(&mut self, evloop: &mut L) { - if SHOULD_EXIT.load(Ordering::Relaxed) { - warn!("exiting peer {}", self.pid); - evloop.disconnect(self.pid, b"error"); - } - for m in self.interface_receive.try_iter() { - match m { - ClientMesgIn::Input(i) => self.input = i, - } - } - } - fn on_packet(&mut self, evloop: &mut L, chunk: Chunk) { - let pid = chunk.pid; - - let msg = match msg::decode(&mut Warn(chunk.data), &mut Unpacker::new(chunk.data)) { - Ok(m) => m, - Err(err) => { - warn!("decode error {:?}:", err); - hexdump(LogLevel::Warn, chunk.data); - return; - } - }; - debug!("{:?}", msg); - match msg { - SystemOrGame::Game(Game::SvMotd(..)) - | SystemOrGame::Game(Game::SvKillMsg(..)) - | SystemOrGame::Game(Game::SvTuneParams(..)) - | SystemOrGame::Game(Game::SvWeaponPickup(..)) - | SystemOrGame::System(System::InputTiming(..)) - | SystemOrGame::Game(Game::SvExtraProjectile(..)) => {} - SystemOrGame::Game(Game::SvChat(chat)) => { - if chat.client_id == -1 { - info!("[server]: {}", pretty::AlmostString::new(chat.message)); - } else { - info!( - "[team {}]: {}", - chat.team, - pretty::AlmostString::new(chat.message) - ) - } - } - SystemOrGame::Game(Game::SvBroadcast(broadcast)) => { - info!( - "broadcast: {}", - pretty::AlmostString::new(broadcast.message) - ); - } - _ => {} - } - - match msg { - SystemOrGame::System(ref msg) => match *msg { - System::MapChange(MapChange { crc, size, name }) => { - if let Some(_) = size.try_usize() { - if name.iter().any(|&b| b == b'/' || b == b'\\') { - error!("invalid map name"); - evloop.disconnect(pid, b"error"); - return; - } - match self.state { - ClientState::MapChange => {} - ClientState::ReadyToEnter if self.dummy_map => {} - _ => warn!("map change from state {:?}", self.state), - } - self.dummy_map = check_dummy_map(name, crc as u32, size); - self.current_votes.clear(); - self.num_snaps_since_reset = 0; - self.snaps.reset(); - info!("map change: {}", pretty::AlmostString::new(name)); - let name = String::from_utf8_lossy(name); - if let Cow::Owned(..) = name { - warn!("weird characters in map name"); - } - let mut start_download = false; - if need_file(crc, &name) { - if let Err(e) = self.open_download_file(crc, name.borrow()) { - error!("error opening file {:?}", e); - } else { - start_download = true; - } - } - if start_download { - info!("download starting"); - evloop.send_system(pid, RequestMapData { chunk: 0 }); - self.state = ClientState::MapData(crc, 0); - } else { - self.state = ClientState::ConReady; - evloop.send_system(pid, Ready); - - self.interface_send - .send(ClientMesgOut::MapChange { - name: name.to_string(), - crc, - }) - .unwrap(); - } - } else { - error!("invalid map size"); - evloop.disconnect(pid, b"error"); - return; - } - } - System::Snap(_) | System::SnapEmpty(_) | System::SnapSingle(_) => { - self.num_snaps_since_reset += 1; - { - let a = gamenet::msg::system::Snap; - let b = gamenet_ddnet::msg::system::Snap; - let res = match *msg { - System::Snap(s) => self.snaps.snap(&mut Log, obj_size, s), - System::SnapEmpty(s) => self.snaps.snap_empty(&mut Log, obj_size, s), - System::SnapSingle(s) => self.snaps.snap_single(&mut Log, obj_size, s), - _ => unreachable!(), - }; - match res { - Ok(Some(snap)) => { - let snaps = snap - .items() - .filter_map(|item| { - SnapObj::decode_obj( - &mut WarnSnap(item), - item.type_id.into(), - &mut IntUnpacker::new(item.data), - ) - .map(|o| (item.id, o)) - .ok() - }) - .collect::>(); - - self.interface_send - .send(ClientMesgOut::Snaps(snaps.clone())) - .unwrap(); - } - Ok(None) => { - self.num_snaps_since_reset -= 1; - } - Err(err) => warn!("snapshot error {:?}", err), - } - } - // DDNet needs the INPUT message as the first - // chunk of the packet. - evloop.force_flush(pid); - let tick = self.snaps.ack_tick().unwrap_or(-1); - evloop.send_system( - pid, - Input { - ack_snapshot: tick, - intended_tick: tick, - input_size: mem::size_of::().assert_i32(), - input: self.input, - }, - ); - } - _ => {} - }, - SystemOrGame::Game(ref msg) => match *msg { - _ => {} - }, - } - match self.state { - ClientState::Connection => unreachable!(), - ClientState::MapChange => {} // Handled above. - ClientState::MapData(cur_crc, cur_chunk) => match msg { - SystemOrGame::System(System::MapData(MapData { - last, - crc, - chunk, - data, - })) => { - if cur_crc == crc && cur_chunk == chunk { - let res = self.write_download_file(data); - if let Err(ref err) = res { - error!("error writing file {:?}", err); - } - if last != 0 || res.is_err() { - if !res.is_err() { - if let Err(err) = self.finish_download_file() { - error!("error finishing file {:?}", err); - } - if last != 1 { - warn!("weird map data packet"); - } - } - self.state = ClientState::ConReady; - evloop.send_system(pid, Ready); - - info!("download finished"); - } else { - let cur_chunk = cur_chunk.checked_add(1).unwrap(); - self.state = ClientState::MapData(cur_crc, cur_chunk); - evloop.send_system(pid, RequestMapData { chunk: cur_chunk }); - } - } else { - if cur_crc != crc || cur_chunk < chunk { - warn!("unsolicited map data crc={:08x} chunk={}", crc, chunk); - warn!("want crc={:08x} chunk={}", cur_crc, cur_chunk); - } - } - } - _ => {} - }, - ClientState::ConReady => match msg { - SystemOrGame::System(System::ConReady(..)) => { - evloop.send_game( - pid, - ClStartInfo { - name: self.config.nick.as_bytes(), - clan: self.config.clan.as_bytes(), - country: -1, - skin: b"limekittygirl", - use_custom_color: true, - color_body: 0xFF00FF, - color_feet: 0x550055, - }, - ); - self.state = ClientState::ReadyToEnter; - } - _ => {} - }, - ClientState::ReadyToEnter => match msg { - SystemOrGame::Game(Game::SvReadyToEnter(..)) => { - evloop.send_system(pid, EnterGame); - evloop.send_game(pid, ClSetTeam { team: Team::Red }); - } - _ => {} - }, - } - evloop.flush(pid); - } - fn on_connless_packet(&mut self, _: &mut L, chunk: ConnlessChunk) { - warn!( - "connless packet {} {:?}", - chunk.addr, - pretty::Bytes::new(chunk.data) - ); - } - fn on_connect(&mut self, _: &mut L, _: PeerId) { - unreachable!(); - } - fn on_ready(&mut self, evloop: &mut L, pid: PeerId) { - if pid != self.pid { - error!("not our pid: {} vs {}", pid, self.pid); - } - self.state = ClientState::MapChange; - evloop.send_system( - pid, - Info { - version: VERSION.as_bytes(), - password: Some(b""), - }, - ); - evloop.flush(pid); - } - fn on_disconnect(&mut self, _: &mut L, pid: PeerId, remote: bool, reason: &[u8]) { - if remote { - error!( - "disconnected pid={:?} error={}", - pid, - pretty::AlmostString::new(reason) - ); - } - } -} - -struct Download { - file: NamedTempFile, - crc: i32, - name: String, -} - -impl Client { - fn open_download_file(&mut self, crc: i32, name: &str) -> Result<(), io::Error> { - self.download = Some(Download { - file: NamedTempFileOptions::new() - .prefix(&format!("{}_{:08x}_", name, crc)) - .suffix(".map") - .create_in("/tmp/downloading")?, - crc, - name: name.to_string(), - }); - Ok(()) - } - fn write_download_file(&mut self, data: &[u8]) -> Result<(), io::Error> { - self.download.as_mut().unwrap().file.write_all(data) - } - fn finish_download_file(&mut self) -> Result<(), io::Error> { - let download = self.download.take().unwrap(); - let path = get_map_path(download.name.as_str(), download.crc); - download - .file - .persist(&path) - .map(|_| ()) - .map_err(|e| e.error)?; - Ok(()) - } -} diff --git a/client/src/lib.rs b/client/src/lib.rs deleted file mode 100644 index 3c19df7..0000000 --- a/client/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![feature(exclusive_range_pattern)] - -pub mod client; -pub mod world; - -#[cfg(feature = "gamenet_ddnet_0_6")] -pub extern crate gamenet_ddnet as gamenet; -#[cfg(feature = "gamenet_0_5")] -pub extern crate gamenet_teeworlds_0_5 as gamenet; -#[cfg(feature = "gamenet_0_6")] -pub extern crate gamenet_teeworlds_0_6 as gamenet; -#[cfg(feature = "gamenet_0_7")] -pub extern crate gamenet_teeworlds_0_7 as gamenet; - - -use std::sync::atomic::AtomicBool; - -use lazy_static::lazy_static; -lazy_static! { - pub static ref SHOULD_EXIT: AtomicBool = AtomicBool::new(false); -} diff --git a/client/src/main.rs b/client/src/main.rs index b79f338..e7a11a9 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,41 +1,3 @@ -use log::{error, log, warn}; -use signal_hook::{ - consts::{SIGINT, SIGTERM}, - iterator::Signals, - low_level::exit, -}; -use std::{net::IpAddr, str::FromStr, sync::atomic::Ordering, thread, time::Duration}; -use twclient::{ - client::{Client, ClientConfig}, - SHOULD_EXIT, -}; - fn main() { - env_logger::init(); - - let mut signals = Signals::new(&[SIGTERM, SIGINT]).unwrap(); - 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 config = ClientConfig { - nick: String::from("metamuffin"), - clan: String::from("rustacean"), - timeout: String::from("asgefdhjikhjfhjf"), - }; - 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(); - drop(ip); - drop(port); - drop(config); - drop(Client::new_evloop()); - todo!() - // Client::run_thing(ip, port, config) + println!("Hello, world!"); } diff --git a/client/src/world/helper.rs b/client/src/world/helper.rs deleted file mode 100644 index cb34a9f..0000000 --- a/client/src/world/helper.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::fmt::Display; - -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] -#[repr(C)] -pub struct Color { - pub a: u8, - pub r: u8, - pub g: u8, - pub b: u8, -} - -impl Display for Color { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!( - "#{:02x}{:02x}{:02x}{:02x}", - self.r, self.g, self.b, self.a - )) - } -} diff --git a/client/src/world/map.rs b/client/src/world/map.rs deleted file mode 100644 index c8522b9..0000000 --- a/client/src/world/map.rs +++ /dev/null @@ -1,158 +0,0 @@ -use ::map as mapfile; -use anyhow::Error; -use common::{num::Cast, pretty, vec}; -use log::{info, log, warn}; -use ndarray::Array2; -use std::{ - collections::{hash_map, HashMap}, - fs::File, - mem, -}; - -pub use mapfile::format; -pub use mapfile::{ - format::Tile, - reader::{self, Color as BadColor, LayerTilemapType}, -}; - -use super::helper::Color; -pub const TILE_NUM: u32 = 16; - -pub struct Layer { - pub color: Color, - pub image: Option, - pub tiles: Array2, - pub kind: LayerTilemapType, - pub offset: (i32, i32), -} - -pub struct Map { - pub layers: Vec, - pub tilesets: HashMap, Array2>, - pub name: String, - pub crc: i32, -} - -impl Map { - pub fn empty() -> Self { - Self { - layers: Vec::new(), - tilesets: HashMap::new(), - crc: 0, - name: String::from("twclient dummy map"), - } - } - - pub fn load(file: File, name: &str, crc: i32) -> Result { - info!("loading map"); - let datafile = datafile::Reader::new(file).unwrap(); - let mut map = mapfile::Reader::from_datafile(datafile); - - let mut layers = vec![]; - for group_idx in map.group_indices() { - let group = map.group(group_idx).unwrap(); - - if group.parallax_x != 100 || group.parallax_y != 100 || group.clipping.is_some() { - warn!( - "skipping layer: {}: parallax_x:{} parallax_y:{} clipping:{:?}", - pretty::AlmostString::new(&group.name), - group.parallax_x, - group.parallax_y, - group.clipping, - ); - continue; - } - - for layer_idx in group.layer_indices { - info!("loading {}", pretty::AlmostString::new(&group.name)); - let layer = map.layer(layer_idx).unwrap(); - let tilemap = if let reader::LayerType::Tilemap(t) = layer.t { - t - } else { - warn!("non-tilemap layer skipped"); - continue; - }; - let normal = if let Some(n) = tilemap.type_.to_normal() { - n - } else { - warn!("non-normal layer skipped"); - continue; - }; - let tiles = map.layer_tiles(tilemap.tiles(normal.data)).unwrap(); - layers.push(Layer { - color: Color { - r: normal.color.red, - g: normal.color.green, - b: normal.color.blue, - a: normal.color.alpha, - }, - image: normal.image, - kind: tilemap.type_, - tiles, - offset: (group.offset_x, group.offset_y), - }); - } - } - - let mut tilesets = HashMap::new(); - for layer in &layers { - match tilesets.entry(layer.image) { - hash_map::Entry::Occupied(_) => {} - hash_map::Entry::Vacant(v) => { - let data = match layer.image { - None => Array2::from_elem( - (1, 1), - Color { - a: 255, - r: 255, - g: 0, - b: 255, - }, - ), - Some(image_idx) => { - let image = map.image(image_idx).unwrap(); - let height = image.height.usize(); - let width = image.width.usize(); - match image.data { - Some(d) => { - let data = map.image_data(d).unwrap(); - if data.len() % mem::size_of::() != 0 { - panic!("image shape invalid"); - } - let data: Vec = unsafe { vec::transmute(data) }; - Array2::from_shape_vec((height, width), data).unwrap() - } - None => { - warn!("layer with external tileset skipped"); - continue; - // let image_name = map.image_name(image.name)?; - // // WARN? Unknown external image - // // WARN! Wrong dimensions - // str::from_utf8(&image_name).ok() - // .and_then(sanitize) - // .map(&mut external_tileset_loader) - // .transpose()? - // .unwrap_or(None) - // .unwrap_or_else(|| Array2::from_elem((1, 1), Color::white())) - } - } - } - }; - v.insert(data); - } - } - } - - info!( - "{} layers + {} tilesets loaded", - layers.len(), - tilesets.len() - ); - Ok(Self { - tilesets, - layers, - crc, - name: String::from(name), - }) - } -} diff --git a/client/src/world/mod.rs b/client/src/world/mod.rs deleted file mode 100644 index 7b25a53..0000000 --- a/client/src/world/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -use self::{map::Map, tee::Tees}; -use crate::client::{helper::get_map_path, ClientMesgOut}; -use std::fs::File; - -pub mod helper; -pub mod map; -pub mod tee; - -pub use gamenet::enums; - -pub struct World { - pub map: Map, - pub tees: Tees, -} - -impl World { - pub fn new() -> Self { - Self { - map: Map::empty(), - tees: Tees::new(), - } - } - - pub fn update(&mut self, m: &ClientMesgOut) { - self.tees.update(m); - match m { - ClientMesgOut::MapChange { name, crc } => { - let file = File::open(get_map_path(name.as_str(), *crc)).unwrap(); - self.map = Map::load(file, name.as_str(), *crc).unwrap(); - } - _ => (), - } - } -} diff --git a/client/src/world/tee.rs b/client/src/world/tee.rs deleted file mode 100644 index 6b33f48..0000000 --- a/client/src/world/tee.rs +++ /dev/null @@ -1,167 +0,0 @@ -use std::{collections::BTreeMap, f32::consts::PI}; - -use super::helper::Color; -use crate::client::ClientMesgOut; -use gamenet::{ - enums::{Emote, Team, Weapon}, - SnapObj, -}; - -pub struct Tees { - pub inner: BTreeMap, -} - -pub struct Tee { - pub name: String, - pub skin: String, - pub clan: String, - - pub local: bool, - pub latency: i32, - pub score: i32, - - pub team: Team, - pub weapon: Weapon, - pub armor: i32, - pub ammo: i32, - pub emote: Emote, - pub attack_tick: i32, - - pub tick: i32, - pub angle: f32, - pub x: i32, - pub y: i32, - pub vel_x: i32, - pub vel_y: i32, - pub hook_x: i32, - pub hook_y: i32, - pub hook_dx: i32, - pub hook_dy: i32, - pub hook_player: i32, - pub hook_state: i32, - - pub color_feet: Color, - pub color_body: Color, - pub use_custom_colors: bool, - pub country: i32, -} - -impl Default for Tee { - fn default() -> Self { - Self { - x: Default::default(), - y: Default::default(), - local: false, - team: Team::Spectators, - latency: Default::default(), - score: Default::default(), - weapon: Weapon::Shotgun, - armor: Default::default(), - ammo: Default::default(), - attack_tick: Default::default(), - emote: Emote::Normal, - tick: Default::default(), - angle: Default::default(), - vel_x: Default::default(), - vel_y: Default::default(), - hook_x: Default::default(), - hook_y: Default::default(), - hook_dx: Default::default(), - hook_dy: Default::default(), - hook_player: Default::default(), - hook_state: Default::default(), - name: Default::default(), - skin: Default::default(), - clan: Default::default(), - color_feet: Color { - a: 0, - b: 0, - g: 0, - r: 0, - }, - color_body: Color { - a: 0, - b: 0, - g: 0, - r: 0, - }, - use_custom_colors: Default::default(), - country: Default::default(), - } - } -} - -impl Tees { - pub fn new() -> Self { - Self { - inner: BTreeMap::new(), - } - } - pub fn update(&mut self, m: &ClientMesgOut) { - match m { - ClientMesgOut::Snaps(s) => { - self.inner.clear(); - for (id, o) in s { - let e = self.inner.entry(*id).or_default(); - match o { - SnapObj::ClientInfo(o) => { - e.name = i32_to_string(o.name); - e.skin = i32_to_string(o.skin); - e.clan = i32_to_string(o.clan); - e.color_feet = unsafe { std::mem::transmute(o.color_feet) }; - e.color_body = unsafe { std::mem::transmute(o.color_body) }; - e.use_custom_colors = o.use_custom_color != 0 - } - SnapObj::PlayerInfo(o) => { - e.local = o.local == 1; - e.team = o.team; - e.latency = o.latency; - e.score = o.score; - } - SnapObj::Character(c) => { - e.ammo = c.ammo_count; - e.weapon = c.weapon; - e.emote = c.emote; - e.attack_tick = c.attack_tick; - - e.x = c.character_core.x; - e.y = c.character_core.y; - e.angle = c.character_core.angle as f32 / 1600.0 * 2.0 * PI; - e.vel_x = c.character_core.vel_x; - e.vel_y = c.character_core.vel_y; - - e.tick = c.character_core.tick; - e.hook_x = c.character_core.hook_x; - e.hook_y = c.character_core.hook_y; - e.hook_player = c.character_core.hooked_player; - e.hook_dx = c.character_core.hook_dx; - e.hook_dy = c.character_core.hook_dy; - e.hook_state = c.character_core.hook_state; - } - _ => (), - } - } - } - _ => {} - } - } - - pub fn local(&self) -> Option<&Tee> { - self.inner.values().find(|e| e.local) - } -} - -fn i32_to_string(k: [i32; S]) -> String { - let mut bytes = vec![]; - for i in 0..S { - bytes.push(((((k[i]) >> 24) & 0xff) - 128) as u8); - bytes.push(((((k[i]) >> 16) & 0xff) - 128) as u8); - bytes.push(((((k[i]) >> 8) & 0xff) - 128) as u8); - bytes.push((((k[i]) & 0xff) - 128) as u8); - } - let len = bytes.iter().position(|e| *e == 0).unwrap_or(S); - while bytes.len() > len { - bytes.pop(); - } - String::from_utf8(bytes).unwrap() -} diff --git a/readme.md b/readme.md index e9ab085..2c499dc 100644 --- a/readme.md +++ b/readme.md @@ -1,9 +1,7 @@ # twclient +DDNet client in Rust. + ## Licence -``` -GNU Affero General Public Licence (version 3 only)! -Copyright (c) 2022 metamuffin -Based on MIT licenced code of libtw2 by heinrich5991 -``` +AGPL-3.0-only; See [COPYING](./COPYING) diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml deleted file mode 100644 index d78190d..0000000 --- a/renderer/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "twrenderer" -version = "0.1.0" -edition = "2021" -authors = [ - "metamuffin ", - "heinrich5991 ", -] -license = "AGPL-3.0-only" - -[dependencies] -twclient = { path = "../client", features = ["gamenet_ddnet_0_6"]} -skia-safe = { version = "0.52.0", features = ["gl", "wayland"] } -glutin = "0.28.0" -gl = "0.14.0" -log = "0.4.17" -env_logger = "0.9.0" -signal-hook = "0.3.14" diff --git a/renderer/src/main.rs b/renderer/src/main.rs deleted file mode 100644 index 186b773..0000000 --- a/renderer/src/main.rs +++ /dev/null @@ -1,278 +0,0 @@ -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::MouseInput { button, state, .. } => match state { - ElementState::Pressed => renderer.input.fire = 1, - ElementState::Released => renderer.input.fire = 0, - }, - WindowEvent::CursorMoved { position, .. } => {} - 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; - } - VirtualKeyCode::D => { - renderer.input.direction += sk; - } - VirtualKeyCode::Space => { - renderer.input.jump = sa; - } - _ => (), - } - } - } - } - _ => (), - }, - 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) { - self.client_interface - .send - .send(ClientMesgIn::Input(self.input)) - .unwrap(); - 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)); - // canvas.scale((0.1, 0.1)); - 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() -} diff --git a/renderer/src/map.rs b/renderer/src/map.rs deleted file mode 100644 index db5dd53..0000000 --- a/renderer/src/map.rs +++ /dev/null @@ -1,161 +0,0 @@ -use std::collections::HashMap; - -use log::info; -use skia_safe::{ - canvas::SrcRectConstraint, Canvas, Color4f, ColorSpace, ISize, Image, Matrix, Paint, Rect, -}; -use twclient::world::{ - helper::Color, - map::{format, TILE_NUM}, - World, -}; - -pub struct MapRenderer { - tileset: HashMap<(Option, Color), Image>, -} - -const TILE_SIZE: f32 = 32.0; - -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 { - for layer in world.map.layers.iter().filter(|l| l.image == *key) { - let tint = layer.color; - info!( - "loading tileset: (texture: {:?}, tint: {}) => {:?}", - key, - tint, - t.dim() - ); - let mut bytes: Vec = Vec::with_capacity(t.dim().0 * t.dim().1 * 4); - for ((_x, _y), c) in t.indexed_iter() { - bytes.push((c.r as u32/* * tint.r as u32*/) as u8); - bytes.push((c.g as u32/* * tint.g as u32*/) as u8); - bytes.push((c.b as u32/* * tint.b as u32*/) as u8); - bytes.push((c.a as u32/* * tint.a as u32*/) as u8); - } - 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::Premul, - ColorSpace::new_srgb_linear(), - ), - d, - t.dim().0 * 4, - ) - .unwrap(); - self.tileset.insert((*key, tint), v); - } - } - } - - pub fn draw(&self, world: &World, canvas: &mut Canvas) { - let draw_distance = 40; - let rot90 = Matrix::rotate_deg(90.0); - 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 - .tees - .local() - .map(|t| (t.x / 32, t.y / 32)) - .unwrap_or((0, 0)); - - for l in &world.map.layers { - let tileset = self.tileset.get(&(l.image, l.color)).unwrap(); - - let mut layer_tint = Paint::new( - Color4f { - a: 1.0, - b: 1.0, - g: 1.0, - r: 1.0, - }, - &ColorSpace::new_srgb(), - ); - layer_tint.set_style(skia_safe::PaintStyle::Fill); - - for layer_y in (center.1 - draw_distance)..(center.1 + draw_distance) { - for layer_x in (center.0 - draw_distance)..(center.0 + draw_distance) { - let layer_x = layer_x.try_into().unwrap_or(0); - let layer_y = layer_y.try_into().unwrap_or(0); - - let tile = match l.tiles.get(( - ((layer_y as i32) + l.offset.1) as usize, - ((layer_y as i32) + l.offset.0) as usize, - )) { - Some(t) => t, - None => continue, - }; - - 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; - } - - canvas.save(); - canvas.translate(( - (layer_x as f32 + 0.5) * TILE_SIZE, - (layer_y as f32 + 0.5) * TILE_SIZE, - )); - if rotate { - canvas.concat(&rot90); - } - if vflip { - canvas.scale((-1.0, 1.0)); - } - if hflip { - canvas.scale((1.0, -1.0)); - } - - const TL: u32 = 64; - canvas.draw_image_rect( - tileset, - 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, - )), - Rect { - top: -TILE_SIZE / 2.0, - bottom: TILE_SIZE / 2.0, - left: -TILE_SIZE / 2.0, - right: TILE_SIZE / 2.0, - }, - &layer_tint, - ); - - canvas.restore(); - } - } - } - } -} diff --git a/renderer/src/tee.rs b/renderer/src/tee.rs deleted file mode 100644 index 4d9d945..0000000 --- a/renderer/src/tee.rs +++ /dev/null @@ -1,206 +0,0 @@ -use std::{collections::BTreeMap, fs::File, io::Read}; - -use skia_safe::{ - canvas::SrcRectConstraint, utils::text_utils::Align, Canvas, Color4f, ColorSpace, Data, Font, - Image, Paint, Point, Rect, -}; -use twclient::world::{tee::Tee, World}; - -// const TEE_COLL_RADIUS: f32 = 16.0; -const TEE_REND_RADIUS: f32 = 32.0; -const TEE_EYE_ROTATION_RADIUS: f32 = 8.0; -const TEE_EYE_SIZE: f32 = 12.0; -const TEE_EYE_DISTANCE: f32 = 8.0; -const TEE_FOOT_SIZE: f32 = 14.0; -const TEE_EYE_OFFSET_Y: f32 = -2.0; - -pub struct TeeRenderer { - skins: BTreeMap, -} - -impl TeeRenderer { - pub fn new() -> Self { - Self { - skins: BTreeMap::new(), - } - } - - pub fn draw(&mut self, world: &World, canvas: &mut Canvas) { - for t in world.tees.inner.values() { - canvas.save(); - canvas.translate((t.x as f32, t.y as f32)); - self.draw_tee(canvas, t); - canvas.restore(); - } - } - - pub fn draw_tee(&mut self, canvas: &mut Canvas, tee: &Tee) { - let tee_paint = Paint::new( - Color4f { - a: 1.0, - r: 1.0, - g: 1.0, - b: 1.0, - }, - &ColorSpace::new_srgb(), - ); - let name_paint = Paint::new( - Color4f { - a: 1.0, - r: 1.0, - g: 1.0, - b: 0.0, - }, - &ColorSpace::new_srgb(), - ); - let skin_texture = self - .skins - .entry(tee.skin.clone()) - .or_insert_with(|| TeeRenderer::load_skin(tee.name.as_str()).unwrap()); - - let origin = Point { - x: tee.x as f32, - y: tee.y as f32, - }; - - { - canvas.save(); - - canvas.draw_image_rect( - &skin_texture, - Some((&SKIN_FOOT, SrcRectConstraint::Strict)), - Rect { - top: -TEE_FOOT_SIZE + TEE_REND_RADIUS * 0.5, - left: -TEE_FOOT_SIZE * 2.0 - TEE_FOOT_SIZE * 0.5, - right: TEE_FOOT_SIZE * 2.0 - TEE_FOOT_SIZE * 0.5, - bottom: TEE_FOOT_SIZE + TEE_REND_RADIUS * 0.5, - }, - &tee_paint, - ); - - canvas.restore(); - } - - canvas.draw_image_rect( - &skin_texture, - Some((&SKIN_BODY_SHADOW, SrcRectConstraint::Strict)), - Rect { - top: -TEE_REND_RADIUS, - left: -TEE_REND_RADIUS, - right: TEE_REND_RADIUS, - bottom: TEE_REND_RADIUS, - }, - &tee_paint, - ); - canvas.draw_image_rect( - &skin_texture, - Some((&SKIN_BODY, SrcRectConstraint::Strict)), - Rect { - top: -TEE_REND_RADIUS, - left: -TEE_REND_RADIUS, - right: TEE_REND_RADIUS, - bottom: TEE_REND_RADIUS, - }, - &tee_paint, - ); - - { - canvas.save(); - // println!("{}", tee.angle); - canvas.translate(( - tee.angle.cos() * TEE_EYE_ROTATION_RADIUS, - tee.angle.sin() * TEE_EYE_ROTATION_RADIUS, - )); - - canvas.draw_image_rect( - &skin_texture, - Some((&SKIN_EYE_NORMAL, SrcRectConstraint::Strict)), - Rect { - top: -TEE_EYE_SIZE + TEE_EYE_OFFSET_Y, - left: -TEE_EYE_SIZE - TEE_EYE_DISTANCE / 2.0, - right: TEE_EYE_SIZE - TEE_EYE_DISTANCE / 2.0, - bottom: TEE_EYE_SIZE + TEE_EYE_OFFSET_Y, - }, - &tee_paint, - ); - { - canvas.save(); - canvas.scale((-1.0, 1.0)); - canvas.draw_image_rect( - &skin_texture, - Some((&SKIN_EYE_NORMAL, SrcRectConstraint::Strict)), - Rect { - top: -TEE_EYE_SIZE + TEE_EYE_OFFSET_Y, - left: -TEE_EYE_SIZE - TEE_EYE_DISTANCE / 2.0, - right: TEE_EYE_SIZE - TEE_EYE_DISTANCE / 2.0, - bottom: TEE_EYE_SIZE + TEE_EYE_OFFSET_Y, - }, - &tee_paint, - ); - canvas.restore(); - } - - canvas.restore(); - } - { - canvas.save(); - - canvas.draw_image_rect( - &skin_texture, - Some((&SKIN_FOOT, SrcRectConstraint::Strict)), - Rect { - top: -TEE_FOOT_SIZE + TEE_REND_RADIUS * 0.5, - left: -TEE_FOOT_SIZE * 2.0 + TEE_FOOT_SIZE * 0.5, - right: TEE_FOOT_SIZE * 2.0 + TEE_FOOT_SIZE * 0.5, - bottom: TEE_FOOT_SIZE + TEE_REND_RADIUS * 0.5, - }, - &tee_paint, - ); - - canvas.restore(); - } - - canvas.draw_str_align( - tee.name.as_str(), - (origin.x, origin.y - 20.0), - &Font::default(), - &name_paint, - Align::Center, - ); - } - - pub fn load_skin(_name: &str) -> Option { - // let path = "/usr/share/ddnet/data/skins/limekitty.png"; - let path = "/home/muffin/.teeworlds/downloadedskins/limekittygirl.png"; - let mut file = File::open(path).unwrap(); - let mut data = Vec::new(); - file.read_to_end(&mut data).unwrap(); - let data = Data::new_copy(&data); - Image::from_encoded(data) - } -} - -const SKIN_BODY: Rect = Rect { - top: 0.0, - left: 0.0, - right: 96.0, - bottom: 96.0, -}; -const SKIN_BODY_SHADOW: Rect = Rect { - top: 0.0, - left: 96.0, - right: 192.0, - bottom: 96.0, -}; -const SKIN_FOOT: Rect = Rect { - left: 196.0, - right: 256.0, - top: 32.0, - bottom: 64.0, -}; -const SKIN_EYE_NORMAL: Rect = Rect { - left: 64.0, - right: 96.0, - top: 96.0, - bottom: 128.0, -}; diff --git a/snapshot/Cargo.toml b/snapshot/Cargo.toml deleted file mode 100644 index 165ba3c..0000000 --- a/snapshot/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "snapshot" -version = "0.0.1" -authors = [ - "metamuffin ", - "heinrich5991 ", -] -license = "AGPL-3.0-only" - -[dependencies] -buffer = "0.1.9" -common = { git = "https://github.com/heinrich5991/libtw2" } -gamenet_teeworlds_0_5 = { git = "https://github.com/heinrich5991/libtw2", optional = true } -gamenet_teeworlds_0_6 = { git = "https://github.com/heinrich5991/libtw2", optional = true } -gamenet_teeworlds_0_7 = { git = "https://github.com/heinrich5991/libtw2", optional = true } -gamenet_ddnet = { git = "https://github.com/heinrich5991/libtw2", optional = true } -packer = { git = "https://github.com/heinrich5991/libtw2" } -vec_map = "0.8.0" -warn = ">=0.1.1,<0.3.0" - - -[features] -default = ["gamenet_ddnet"] -gamenet_tw_0_5 = ["gamenet_teeworlds_0_5"] -gamenet_tw_0_6 = ["gamenet_teeworlds_0_6"] -gamenet_tw_0_7 = ["gamenet_teeworlds_0_7"] -gamenet_ddnet_0_6 = ["gamenet_ddnet"] diff --git a/snapshot/src/format.rs b/snapshot/src/format.rs deleted file mode 100644 index 1a22938..0000000 --- a/snapshot/src/format.rs +++ /dev/null @@ -1,85 +0,0 @@ -use buffer::CapacityError; -use packer::Packer; -use packer::Unpacker; -use packer; -use snap::Error; -use warn::Warn; -use warn::wrap; - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Warning { - Packer(packer::Warning), - NonZeroPadding, - DuplicateDelete, - DuplicateUpdate, - UnknownDelete, - DeleteUpdate, - NumUpdatedItems, -} - -impl From for Warning { - fn from(w: packer::Warning) -> Warning { - Warning::Packer(w) - } -} - -pub fn key_to_type_id(key: i32) -> u16 { - ((key as u32 >> 16) & 0xffff) as u16 -} - -pub fn key_to_id(key: i32) -> u16 { - ((key as u32) & 0xffff) as u16 -} - -pub fn key(type_id: u16, id: u16) -> i32 { - (((type_id as u32) << 16) | (id as u32)) as i32 -} - -#[derive(Clone, Copy, Debug)] -pub struct Item<'a> { - pub type_id: u16, - pub id: u16, - pub data: &'a [i32], -} - -impl<'a> Item<'a> { - pub fn from_key(key: i32, data: &[i32]) -> Item { - Item { - type_id: key_to_type_id(key), - id: key_to_id(key), - data: data, - } - } - pub fn key(&self) -> i32 { - key(self.type_id, self.id) - } -} - -#[derive(Clone, Copy, Debug)] -pub struct DeltaHeader { - pub num_deleted_items: i32, - pub num_updated_items: i32, -} - -impl DeltaHeader { - pub fn decode>(warn: &mut W, p: &mut Unpacker) - -> Result - { - let result = DeltaHeader { - num_deleted_items: packer::positive(p.read_int(wrap(warn))?)?, - num_updated_items: packer::positive(p.read_int(wrap(warn))?)?, - }; - if p.read_int(wrap(warn))? != 0 { - warn.warn(Warning::NonZeroPadding); - } - Ok(result) - } - pub fn encode<'d, 's>(&self, mut p: Packer<'d, 's>) - -> Result<&'d [u8], CapacityError> - { - p.write_int(self.num_deleted_items)?; - p.write_int(self.num_updated_items)?; - p.write_int(0)?; - Ok(p.written()) - } -} diff --git a/snapshot/src/lib.rs b/snapshot/src/lib.rs deleted file mode 100644 index 992d898..0000000 --- a/snapshot/src/lib.rs +++ /dev/null @@ -1,36 +0,0 @@ - -#[cfg(feature = "gamenet_ddnet")] -extern crate gamenet_ddnet as gamenet; -#[cfg(feature = "gamenet_0_5")] -extern crate gamenet_teeworlds_0_5 as gamenet; -#[cfg(feature = "gamenet_0_6")] -extern crate gamenet_teeworlds_0_6 as gamenet; -#[cfg(feature = "gamenet_0_7")] -extern crate gamenet_teeworlds_0_7 as gamenet; - -extern crate buffer; -extern crate common; -extern crate packer; -extern crate vec_map; -extern crate warn; - -pub mod format; -pub mod manager; -pub mod receiver; -pub mod snap; -pub mod storage; - -pub use manager::Manager; -pub use receiver::DeltaReceiver; -pub use receiver::ReceivedDelta; -pub use snap::Delta; -pub use snap::DeltaReader; -pub use snap::Snap; -pub use storage::Storage; - -use common::num::Cast; -use std::ops; - -fn to_usize(r: ops::Range) -> ops::Range { - r.start.usize()..r.end.usize() -} diff --git a/snapshot/src/manager.rs b/snapshot/src/manager.rs deleted file mode 100644 index 65d0c72..0000000 --- a/snapshot/src/manager.rs +++ /dev/null @@ -1,140 +0,0 @@ -use Delta; -use DeltaReader; -use DeltaReceiver; -use ReceivedDelta; -use Snap; -use Storage; -use format; -use gamenet::msg::system; -use packer::Unpacker; -use receiver; -use snap; -use storage; -use warn::Warn; -use warn::wrap; - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Error { - Receiver(receiver::Error), - Snap(snap::Error), - Storage(storage::Error), -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Warning { - Receiver(receiver::Warning), - Snap(format::Warning), - Storage(storage::Warning), -} - -impl From for Error { - fn from(err: receiver::Error) -> Error { - Error::Receiver(err) - } -} - -impl From for Error { - fn from(err: snap::Error) -> Error { - Error::Snap(err) - } -} - -impl From for Error { - fn from(err: storage::Error) -> Error { - Error::Storage(err) - } -} - -impl From for Warning { - fn from(w: receiver::Warning) -> Warning { - Warning::Receiver(w) - } -} - -impl From for Warning { - fn from(w: format::Warning) -> Warning { - Warning::Snap(w) - } -} - -impl From for Warning { - fn from(w: storage::Warning) -> Warning { - Warning::Storage(w) - } -} - -#[derive(Clone, Default)] -struct ManagerInner { - temp_delta: Delta, - reader: DeltaReader, - storage: Storage, -} - -#[derive(Clone, Default)] -pub struct Manager { - inner: ManagerInner, - receiver: DeltaReceiver, -} - -impl Manager { - pub fn new() -> Manager { - Default::default() - } - pub fn reset(&mut self) { - self.inner.storage.reset(); - self.receiver.reset(); - } - pub fn ack_tick(&self) -> Option { - self.inner.storage.ack_tick() - } - pub fn snap_empty(&mut self, warn: &mut W, object_size: O, snap: system::SnapEmpty) - -> Result, Error> - where W: Warn, - O: FnMut(u16) -> Option, - { - let res = self.receiver.snap_empty(wrap(warn), snap); - self.inner.handle_msg(warn, object_size, res) - } - pub fn snap_single(&mut self, warn: &mut W, object_size: O, snap: system::SnapSingle) - -> Result, Error> - where W: Warn, - O: FnMut(u16) -> Option, - { - let res = self.receiver.snap_single(wrap(warn), snap); - self.inner.handle_msg(warn, object_size, res) - } - pub fn snap(&mut self, warn: &mut W, object_size: O, snap: system::Snap) - -> Result, Error> - where W: Warn, - O: FnMut(u16) -> Option, - { - let res = self.receiver.snap(wrap(warn), snap); - self.inner.handle_msg(warn, object_size, res) - } -} - -impl ManagerInner { - fn handle_msg(&mut self, warn: &mut W, object_size: O, res: Result, receiver::Error>) - -> Result, Error> - where W: Warn, - O: FnMut(u16) -> Option, - { - Ok(match res? { - Some(delta) => Some(self.add_delta(warn, object_size, delta)?), - None => None, - }) - } - fn add_delta(&mut self, warn: &mut W, object_size: O, delta: ReceivedDelta) - -> Result<&Snap, Error> - where W: Warn, - O: FnMut(u16) -> Option, - { - let crc = delta.data_and_crc.map(|d| d.1); - if let Some((data, _)) = delta.data_and_crc { - self.reader.read(wrap(warn), &mut self.temp_delta, object_size, &mut Unpacker::new(data))?; - } else { - self.temp_delta.clear(); - } - Ok(self.storage.add_delta(wrap(warn), crc, delta.delta_tick, delta.tick, &self.temp_delta)?) - } -} diff --git a/snapshot/src/receiver.rs b/snapshot/src/receiver.rs deleted file mode 100644 index 49833d1..0000000 --- a/snapshot/src/receiver.rs +++ /dev/null @@ -1,245 +0,0 @@ -use common::num::Cast; -use gamenet::msg::system; -use std::ops; -use to_usize; -use vec_map::VecMap; -use warn::Warn; - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Warning { - DuplicateSnap, - DifferingAttributes, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Error { - OldDelta, - InvalidNumParts, - InvalidPart, - DuplicatePart, -} - -// TODO: How to handle `tick` overflowing? -#[derive(Clone, Debug)] -struct CurrentDelta { - tick: i32, - delta_tick: i32, - num_parts: i32, - crc: i32, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct ReceivedDelta<'a> { - pub delta_tick: i32, - pub tick: i32, - pub data_and_crc: Option<(&'a [u8], i32)>, -} - -#[derive(Clone, Default)] -pub struct DeltaReceiver { - previous_tick: Option, - current: Option, - // `parts` points into `receive_buf`. - parts: VecMap>, - receive_buf: Vec, - result: Vec, -} - -impl DeltaReceiver { - pub fn new() -> DeltaReceiver { - Default::default() - } - pub fn reset(&mut self) { - self.previous_tick = None; - self.current = None; - } - fn can_receive(&self, tick: i32) -> bool { - self.current.as_ref().map(|c| c.tick <= tick) - .or(self.previous_tick.map(|t| t < tick)) - .unwrap_or(true) - } - fn init_delta(&mut self) { - self.parts.clear(); - self.receive_buf.clear(); - self.result.clear(); - } - fn finish_delta(&mut self, tick: i32) { - self.current = None; - self.previous_tick = Some(tick); - } - pub fn snap_empty(&mut self, warn: &mut W, snap: system::SnapEmpty) - -> Result, Error> - where W: Warn, - { - if !self.can_receive(snap.tick) { - return Err(Error::OldDelta); - } - if self.current.as_ref().map(|c| c.tick == snap.tick).unwrap_or(false) { - warn.warn(Warning::DuplicateSnap); - } - self.init_delta(); - self.finish_delta(snap.tick); - Ok(Some(ReceivedDelta { - delta_tick: snap.tick.wrapping_sub(snap.delta_tick), - tick: snap.tick, - data_and_crc: None, - })) - } - pub fn snap_single(&mut self, warn: &mut W, snap: system::SnapSingle) - -> Result, Error> - where W: Warn, - { - if !self.can_receive(snap.tick) { - return Err(Error::OldDelta); - } - if self.current.as_ref().map(|c| c.tick == snap.tick).unwrap_or(false) { - warn.warn(Warning::DuplicateSnap); - } - self.init_delta(); - self.finish_delta(snap.tick); - self.result.extend(snap.data); - Ok(Some(ReceivedDelta { - delta_tick: snap.tick.wrapping_sub(snap.delta_tick), - tick: snap.tick, - data_and_crc: Some((&self.result, snap.crc)), - })) - } - pub fn snap(&mut self, warn: &mut W, snap: system::Snap) - -> Result, Error> - where W: Warn, - { - if !self.can_receive(snap.tick) { - return Err(Error::OldDelta); - } - if !(0 <= snap.num_parts && snap.num_parts <= 32) { - return Err(Error::InvalidNumParts); - } - if !(0 <= snap.part && snap.part < snap.num_parts) { - return Err(Error::InvalidPart); - } - if self.current.as_ref().map(|c| c.tick != snap.tick).unwrap_or(false) { - self.current = None; - } - if let None = self.current { - self.init_delta(); - self.current = Some(CurrentDelta { - tick: snap.tick, - delta_tick: snap.tick.wrapping_sub(snap.delta_tick), - num_parts: snap.num_parts, - crc: snap.crc, - }); - - // Checked above. - let num_parts = snap.num_parts.assert_usize(); - self.parts.reserve_len(num_parts); - } - let delta_tick; - let tick; - let crc; - let num_parts; - { - let current: &mut CurrentDelta = self.current.as_mut().unwrap(); - if snap.delta_tick != current.delta_tick - || snap.num_parts != current.num_parts - || snap.crc != current.crc - { - warn.warn(Warning::DifferingAttributes); - } - delta_tick = current.delta_tick; - tick = current.tick; - crc = current.crc; - num_parts = current.num_parts; - } - let part = snap.part.assert_usize(); - if self.parts.contains_key(part) { - return Err(Error::DuplicatePart); - } - let start = self.receive_buf.len().assert_u32(); - let end = (self.receive_buf.len() + snap.data.len()).assert_u32(); - self.receive_buf.extend(snap.data); - assert!(self.parts.insert(part, start..end).is_none()); - - if self.parts.len().assert_i32() != num_parts { - return Ok(None); - } - - self.finish_delta(tick); - self.result.reserve(self.receive_buf.len()); - for range in self.parts.values() { - self.result.extend(&self.receive_buf[to_usize(range.clone())]); - } - - Ok(Some(ReceivedDelta { - delta_tick: delta_tick, - tick: tick, - data_and_crc: Some((&self.result, crc)), - })) - } -} - -#[cfg(test)] -mod test { - use common::num::Cast; - use gamenet::msg::system::Snap; - use gamenet::msg::system::SnapEmpty; - use gamenet::msg::system::SnapSingle; - use super::DeltaReceiver; - use super::Error; - use super::ReceivedDelta; - use warn::Panic; - - #[test] - fn old() { - let mut receiver = DeltaReceiver::new(); - { - let result = receiver.snap_empty(&mut Panic, SnapEmpty { - tick: 1, - delta_tick: 2, - }).unwrap(); - - assert_eq!(result, Some(ReceivedDelta { - delta_tick: -1, - tick: 1, - data_and_crc: None, - })); - } - - assert_eq!(receiver.snap_single(&mut Panic, SnapSingle { - tick: 0, - delta_tick: 0, - data: b"123", - crc: 0, - }).unwrap_err(), Error::OldDelta); - } - - #[test] - fn reorder() { - let mut receiver = DeltaReceiver::new(); - let chunks: &[(i32, &[u8])] = &[ - (3, b"3"), - (2, b"2"), - (4, b"4_"), - (1, b"1__"), - (0, b"0"), - ]; - for &(i, c) in chunks { - let result = receiver.snap(&mut Panic, Snap { - tick: 2, - delta_tick: 1, - num_parts: chunks.len().assert_i32(), - part: i, - crc: 3, - data: c, - }).unwrap(); - if i != 0 { - assert_eq!(result, None); - } else { - assert_eq!(result, Some(ReceivedDelta { - delta_tick: 1, - tick: 2, - data_and_crc: Some((b"01__234_", 3)), - })); - } - } - } -} diff --git a/snapshot/src/snap.rs b/snapshot/src/snap.rs deleted file mode 100644 index 7b9768f..0000000 --- a/snapshot/src/snap.rs +++ /dev/null @@ -1,511 +0,0 @@ -use buffer::CapacityError; -use common::num::Cast; -use format::DeltaHeader; -use format::Item; -use format::Warning; -use format::key; -use format::key_to_id; -use format::key_to_type_id; -use gamenet::enums::MAX_SNAPSHOT_PACKSIZE; -use gamenet::msg::system; -use packer::Packer; -use packer::Unpacker; -use packer::with_packer; -use packer; -use std::cmp; -use std::collections::HashMap; -use std::collections::HashSet; -use std::collections::hash_map; -use std::fmt; -use std::iter; -use std::mem; -use std::ops; -use to_usize; -use warn::Warn; -use warn::wrap; - -// TODO: Actually obey this the same way as Teeworlds does. -pub const MAX_SNAPSHOT_SIZE: usize = 64 * 1024; // 64 KB - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Error { - UnexpectedEnd, - IntOutOfRange, - DeletedItemsUnpacking, - ItemDiffsUnpacking, - TypeIdRange, - IdRange, - NegativeSize, - TooLongDiff, - TooLongSnap, - DeltaDifferingSizes, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum BuilderError { - DuplicateKey, - TooLongSnap, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -struct TooLongSnap; - -impl From for Error { - fn from(_: TooLongSnap) -> Error { - Error::TooLongSnap - } -} - -impl From for BuilderError { - fn from(_: TooLongSnap) -> BuilderError { - BuilderError::TooLongSnap - } -} - -impl From for Error { - fn from(_: packer::IntOutOfRange) -> Error { - Error::IntOutOfRange - } -} - -impl From for Error { - fn from(_: packer::UnexpectedEnd) -> Error { - Error::UnexpectedEnd - } -} - -fn apply_delta(in_: Option<&[i32]>, delta: &[i32], out: &mut [i32]) - -> Result<(), Error> -{ - assert!(delta.len() == out.len()); - match in_ { - Some(in_) => { - if in_.len() != out.len() { - return Err(Error::DeltaDifferingSizes); - } - for i in 0..out.len() { - out[i] = in_[i].wrapping_add(delta[i]); - } - } - None => out.copy_from_slice(delta), - } - Ok(()) -} - -fn create_delta(from: Option<&[i32]>, to: &[i32], out: &mut [i32]) { - assert!(to.len() == out.len()); - match from { - Some(from) => { - assert!(from.len() == to.len()); - for i in 0..out.len() { - out[i] = to[i].wrapping_sub(from[i]); - } - }, - None => out.copy_from_slice(to), - } -} - -// TODO: Select a faster hasher? -#[derive(Clone, Default)] -pub struct Snap { - offsets: HashMap>, - buf: Vec, -} - -impl Snap { - pub fn empty() -> Snap { - Default::default() - } - fn clear(&mut self) { - self.offsets.clear(); - self.buf.clear(); - } - fn item_from_offset(&self, offset: ops::Range) -> &[i32] { - &self.buf[to_usize(offset)] - } - pub fn item(&self, type_id: u16, id: u16) -> Option<&[i32]> { - self.offsets.get(&key(type_id, id)).map(|o| &self.buf[to_usize(o.clone())]) - } - pub fn items(&self) -> Items { - Items { - snap: self, - iter: self.offsets.iter(), - } - } - fn prepare_item_vacant<'a>(entry: hash_map::VacantEntry<'a, i32, ops::Range>, buf: &mut Vec, size: usize) - -> Result<&'a mut ops::Range, TooLongSnap> - { - let offset = buf.len(); - if offset + size > MAX_SNAPSHOT_SIZE { - return Err(TooLongSnap); - } - let start = offset.assert_u32(); - let end = (offset + size).assert_u32(); - buf.extend(iter::repeat(0).take(size)); - Ok(entry.insert(start..end)) - } - fn prepare_item(&mut self, type_id: u16, id: u16, size: usize) - -> Result<&mut [i32], Error> - { - let offset = match self.offsets.entry(key(type_id, id)) { - hash_map::Entry::Occupied(o) => o.into_mut(), - hash_map::Entry::Vacant(v) => Snap::prepare_item_vacant(v, &mut self.buf, size)?, - }.clone(); - Ok(&mut self.buf[to_usize(offset)]) - } - pub fn read_with_delta(&mut self, warn: &mut W, from: &Snap, delta: &Delta) - -> Result<(), Error> - where W: Warn, - { - self.clear(); - - let mut num_deletions = 0; - for item in from.items() { - if !delta.deleted_items.contains(&item.key()) { - let out = self.prepare_item(item.type_id, item.id, item.data.len())?; - out.copy_from_slice(item.data); - } else { - num_deletions += 1; - } - } - if num_deletions != delta.deleted_items.len() { - warn.warn(Warning::UnknownDelete); - } - - for (&key, offset) in &delta.updated_items { - let type_id = key_to_type_id(key); - let id = key_to_id(key); - let diff = &delta.buf[to_usize(offset.clone())]; - let out = self.prepare_item(type_id, id, diff.len())?; - let in_ = from.item(type_id, id); - - apply_delta(in_, diff, out)?; - } - Ok(()) - } - pub fn write<'d, 's>(&self, buf: &mut Vec, mut p: Packer<'d, 's>) - -> Result<&'d [u8], CapacityError> - { - let keys = buf; - keys.clear(); - keys.extend(self.offsets.keys().cloned()); - keys.sort_unstable_by_key(|&k| k as u32); - let data_size = self.buf.len() - .checked_add(self.offsets.len()).expect("snap size overflow") - .checked_mul(mem::size_of::()).expect("snap size overflow") - .assert_i32(); - p.write_int(data_size)?; - let num_items = self.offsets.len().assert_i32(); - p.write_int(num_items)?; - - let mut offset = 0; - for &key in &*keys { - p.write_int(offset)?; - let key_offset = self.offsets[&key].clone(); - offset = offset - .checked_add((key_offset.end - key_offset.start + 1).usize() - .checked_mul(mem::size_of::()) - .expect("item size overflow").assert_i32()) - .expect("offset overflow"); - } - for &key in &*keys { - p.write_int(key)?; - for &i in &self.buf[to_usize(self.offsets[&key].clone())] { - p.write_int(i)?; - } - } - Ok(p.written()) - } - pub fn crc(&self) -> i32 { - self.buf.iter().fold(0, |s, &a| s.wrapping_add(a)) - } - pub fn recycle(mut self) -> Builder { - self.clear(); - Builder { - snap: self, - } - } -} - -pub struct Items<'a> { - snap: &'a Snap, - iter: hash_map::Iter<'a, i32, ops::Range>, -} - -impl<'a> Iterator for Items<'a> { - type Item = Item<'a>; - fn next(&mut self) -> Option> { - self.iter.next().map(|(&k, o)| { - Item::from_key(k, self.snap.item_from_offset(o.clone())) - }) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl<'a> ExactSizeIterator for Items<'a> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl fmt::Debug for Snap { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_map().entries(self.items().map( - |Item { type_id, id, data }| ((type_id, id), data) - )).finish() - } -} - -#[derive(Clone, Default)] -pub struct Delta { - deleted_items: HashSet, - updated_items: HashMap>, - buf: Vec, -} - -impl Delta { - pub fn new() -> Delta { - Default::default() - } - pub fn clear(&mut self) { - self.deleted_items.clear(); - self.updated_items.clear(); - self.buf.clear(); - } - fn prepare_update_item(&mut self, type_id: u16, id: u16, size: usize) -> &mut [i32] { - let key = key(type_id, id); - - let offset = self.buf.len(); - let start = offset.assert_u32(); - let end = (offset + size).assert_u32(); - self.buf.extend(iter::repeat(0).take(size)); - assert!(self.updated_items.insert(key, start..end).is_none()); - &mut self.buf[to_usize(start..end)] - } - pub fn create(&mut self, from: &Snap, to: &Snap) { - self.clear(); - for Item { type_id, id, .. } in from.items() { - if to.item(type_id, id).is_none() { - assert!(self.deleted_items.insert(key(type_id, id))); - } - } - for Item { type_id, id, data } in to.items() { - let from_data = from.item(type_id, id); - let out_delta = self.prepare_update_item(type_id, id, data.len()); - create_delta(from_data, data, out_delta); - } - } - pub fn write<'d, 's, O>(&self, object_size: O, mut p: Packer<'d, 's>) - -> Result<&'d [u8], CapacityError> - where O: FnMut(u16) -> Option, - { - let mut object_size = object_size; - with_packer(&mut p, |p| DeltaHeader { - num_deleted_items: self.deleted_items.len().assert_i32(), - num_updated_items: self.updated_items.len().assert_i32() - }.encode(p))?; - for &key in &self.deleted_items { - p.write_int(key)?; - } - for (&key, range) in &self.updated_items { - let data = &self.buf[to_usize(range.clone())]; - let type_id = key_to_type_id(key); - let id = key_to_id(key); - p.write_int(type_id.i32())?; - p.write_int(id.i32())?; - match object_size(type_id) { - Some(size) => assert!(size.usize() == data.len()), - None => p.write_int(data.len().assert_i32())?, - } - for &d in data { - p.write_int(d)?; - } - } - Ok(p.written()) - } -} - -#[derive(Clone, Default)] -pub struct DeltaReader { - buf: Vec, -} - -impl DeltaReader { - pub fn new() -> DeltaReader { - Default::default() - } - fn clear(&mut self) { - self.buf.clear(); - } - pub fn read(&mut self, warn: &mut W, delta: &mut Delta, object_size: O, p: &mut Unpacker) - -> Result<(), Error> - where W: Warn, - O: FnMut(u16) -> Option, - { - delta.clear(); - self.clear(); - - let mut object_size = object_size; - - let header = DeltaHeader::decode(warn, p)?; - while !p.as_slice().is_empty() { - self.buf.push(p.read_int(wrap(warn))?); - } - let split = header.num_deleted_items.assert_usize(); - if split > self.buf.len() { - return Err(Error::DeletedItemsUnpacking); - } - let (deleted_items, buf) = self.buf.split_at(split); - delta.deleted_items.extend(deleted_items); - if deleted_items.len() != delta.deleted_items.len() { - warn.warn(Warning::DuplicateDelete); - } - - let mut num_updates = 0; - let mut buf = buf.iter(); - // FIXME: Use `is_empty`. - while buf.len() != 0 { - let type_id = buf.next().ok_or(Error::ItemDiffsUnpacking)?; - let id = buf.next().ok_or(Error::ItemDiffsUnpacking)?; - - let type_id = type_id.try_u16().ok_or(Error::TypeIdRange)?; - let id = id.try_u16().ok_or(Error::IdRange)?; - - let size = match object_size(type_id) { - Some(s) => s.usize(), - None => { - let s = buf.next().ok_or(Error::ItemDiffsUnpacking)?; - s.try_usize().ok_or(Error::NegativeSize)? - } - }; - - if size > buf.len() { - return Err(Error::ItemDiffsUnpacking); - } - let (data, b) = buf.as_slice().split_at(size); - buf = b.iter(); - - let offset = delta.buf.len(); - let start = offset.try_u32().ok_or(Error::TooLongDiff)?; - let end = (offset + data.len()).try_u32().ok_or(Error::TooLongDiff)?; - delta.buf.extend(data.iter()); - - // In case of conflict, take later update (as the original code does). - if delta.updated_items.insert(key(type_id, id), start..end).is_some() { - warn.warn(Warning::DuplicateUpdate); - } - - if delta.deleted_items.contains(&key(type_id, id)) { - warn.warn(Warning::DeleteUpdate); - } - num_updates += 1; - } - - if num_updates != header.num_updated_items { - warn.warn(Warning::NumUpdatedItems); - } - - Ok(()) - } -} - -#[derive(Default)] -pub struct Builder { - snap: Snap, -} - -impl Builder { - pub fn new() -> Builder { - Default::default() - } - pub fn add_item(&mut self, type_id: u16, id: u16, data: &[i32]) - -> Result<(), BuilderError> - { - let offset = match self.snap.offsets.entry(key(type_id, id)) { - hash_map::Entry::Occupied(..) => return Err(BuilderError::DuplicateKey), - hash_map::Entry::Vacant(v) => { - Snap::prepare_item_vacant(v, &mut self.snap.buf, data.len())? - } - }.clone(); - self.snap.buf[to_usize(offset)].copy_from_slice(data); - Ok(()) - } - pub fn finish(self) -> Snap { - self.snap - } -} - -pub fn delta_chunks(tick: i32, delta_tick: i32, data: &[u8], crc: i32) -> DeltaChunks { - DeltaChunks { - tick: tick, - delta_tick: tick - delta_tick, - crc: crc, - cur_part: if !data.is_empty() { 0 } else { -1 }, - num_parts: ((data.len() + MAX_SNAPSHOT_PACKSIZE as usize - 1) / MAX_SNAPSHOT_PACKSIZE as usize).assert_i32(), - data: data, - } -} - -impl<'a> Into> for SnapMsg<'a> { - fn into(self) -> system::System<'a> { - match self { - SnapMsg::Snap(s) => system::System::Snap(s), - SnapMsg::SnapEmpty(s) => system::System::SnapEmpty(s), - SnapMsg::SnapSingle(s) => system::System::SnapSingle(s), - } - } -} - -#[derive(Clone, Copy)] -pub enum SnapMsg<'a> { - Snap(system::Snap<'a>), - SnapEmpty(system::SnapEmpty), - SnapSingle(system::SnapSingle<'a>), -} - -pub struct DeltaChunks<'a> { - tick: i32, - delta_tick: i32, - crc: i32, - cur_part: i32, - num_parts: i32, - data: &'a [u8], -} - -impl<'a> Iterator for DeltaChunks<'a> { - type Item = SnapMsg<'a>; - fn next(&mut self) -> Option> { - if self.cur_part == self.num_parts { - return None; - } - let result = if self.num_parts == 0 { - SnapMsg::SnapEmpty(system::SnapEmpty { - tick: self.tick, - delta_tick: self.delta_tick, - }) - } else if self.num_parts == 1 { - SnapMsg::SnapSingle(system::SnapSingle { - tick: self.tick, - delta_tick: self.delta_tick, - crc: self.crc, - data: self.data, - }) - } else { - let index = self.cur_part.assert_usize(); - let start = MAX_SNAPSHOT_PACKSIZE as usize * index; - let end = cmp::min(MAX_SNAPSHOT_PACKSIZE as usize * (index + 1), self.data.len()); - SnapMsg::Snap(system::Snap { - tick: self.tick, - delta_tick: self.delta_tick, - num_parts: self.num_parts, - part: self.cur_part, - crc: self.crc, - data: &self.data[start..end], - }) - }; - self.cur_part += 1; - Some(result) - } -} diff --git a/snapshot/src/storage.rs b/snapshot/src/storage.rs deleted file mode 100644 index c445d04..0000000 --- a/snapshot/src/storage.rs +++ /dev/null @@ -1,176 +0,0 @@ -use format; -use snap::Builder; -use snap::Delta; -use snap::Snap; -use snap; -use std::collections::VecDeque; -use warn::Warn; -use warn::wrap; - -// TODO: Separate server storage from client storage. -// TODO: Delete snapshots over time. - -#[derive(Clone)] -struct StoredSnap { - snap: Snap, - tick: i32, -} - -const MAX_STORED_SNAPSHOT: usize = 100; - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct UnknownSnap; - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct WeirdNegativeDeltaTick; - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Error { - OldDelta, - UnknownSnap, - InvalidCrc, - Unpack(snap::Error), -} - -impl From for Error { - fn from(err: snap::Error) -> Error { - Error::Unpack(err) - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Warning { - WeirdNegativeDeltaTick, - Unpack(format::Warning), -} - -impl From for Warning { - fn from(w: format::Warning) -> Warning { - Warning::Unpack(w) - } -} - -#[derive(Clone, Default)] -pub struct Storage { - /// Queue that stores received snaps. - /// - /// The newest elements are in the front. - snaps: VecDeque, - free: Vec, - ack_tick: Option, - delta: Delta, - delta_tick: Option, -} - -impl Storage { - pub fn new() -> Storage { - Default::default() - } - pub fn reset(&mut self) { - let self_free = &mut self.free; - // FIXME: Replace with something like `exhaust`. - self.snaps.drain(..).map(|s| self_free.push(s.snap)).count(); - self.ack_tick = None; - } - pub fn ack_tick(&self) -> Option { - self.ack_tick - } - pub fn add_delta(&mut self, warn: &mut W, crc: Option, delta_tick: i32, tick: i32, delta: &Delta) - -> Result<&Snap, Error> - where W: Warn, - { - if self.snaps.front().map(|s| s.tick).unwrap_or(-1) >= tick { - return Err(Error::OldDelta); - } - { - let empty = Snap::empty(); - let delta_snap; - if delta_tick >= 0 { - if let Some(i) = self.snaps.iter().position(|s| s.tick < delta_tick) { - let self_free = &mut self.free; - // FIXME: Replace with something like `exhaust`. - self.snaps.drain(i..).map(|s| self_free.push(s.snap)).count(); - } - if let Some(d) = self.snaps.back() { - if d.tick == delta_tick { - delta_snap = &d.snap; - } else { - self.ack_tick = None; - return Err(Error::UnknownSnap); - } - } else { - self.ack_tick = None; - return Err(Error::UnknownSnap); - } - } else { - delta_snap = ∅ - if delta_tick != -1 { - warn.warn(Warning::WeirdNegativeDeltaTick); - } - } - if self.free.is_empty() { - self.free.push(Snap::empty()); - } - - let new_snap: &mut Snap = self.free.last_mut().unwrap(); - new_snap.read_with_delta(wrap(warn), delta_snap, delta)?; - if crc.map(|crc| crc != new_snap.crc()).unwrap_or(false) { - self.ack_tick = None; - return Err(Error::InvalidCrc); - } - self.ack_tick = Some(tick); - } - self.snaps.push_front(StoredSnap { - tick: tick, - snap: self.free.pop().unwrap(), - }); - if self.snaps.len() > MAX_STORED_SNAPSHOT { - self.free.push(self.snaps.pop_back().unwrap().snap); - } - Ok(&self.snaps.front().unwrap().snap) - } - pub fn new_builder(&mut self) -> Builder { - self.free.pop().unwrap_or_default().recycle() - } - pub fn set_delta_tick(&mut self, warn: &mut W, tick: i32) - -> Result<(), UnknownSnap> - where W: Warn, - { - if tick < 0 { - if tick != -1 { - warn.warn(WeirdNegativeDeltaTick); - } - self.delta_tick = None; - return Ok(()); - } - if let Some(i) = self.snaps.iter().position(|s| s.tick < tick) { - let self_free = &mut self.free; - // FIXME: Replace with something like `exhaust`. - self.snaps.drain(i..).map(|s| self_free.push(s.snap)).count(); - } - if !self.snaps.back().map(|s| s.tick == tick).unwrap_or(false) { - self.delta_tick = None; - return Err(UnknownSnap); - } - self.delta_tick = Some(tick); - Ok(()) - } - pub fn delta_tick(&self) -> Option { - self.delta_tick - } - pub fn add_snap(&mut self, tick: i32, snap: Snap) -> &Delta { - self.snaps.push_front(StoredSnap { - snap: snap, - tick: tick, - }); - let tmp; - let delta_snap = if self.delta_tick.is_some() { - &self.snaps.back().unwrap().snap - } else { - tmp = Snap::empty(); - &tmp - }; - self.delta.create(delta_snap, &self.snaps.front().unwrap().snap); - &self.delta - } -} -- cgit v1.2.3-70-g09d2