aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--COPYING5
-rw-r--r--Cargo.toml3
-rw-r--r--client/Cargo.toml46
-rw-r--r--client/src/client/helper.rs87
-rw-r--r--client/src/client/mod.rs431
-rw-r--r--client/src/lib.rs21
-rw-r--r--client/src/main.rs40
-rw-r--r--client/src/world/helper.rs19
-rw-r--r--client/src/world/map.rs158
-rw-r--r--client/src/world/mod.rs34
-rw-r--r--client/src/world/tee.rs167
-rw-r--r--readme.md8
-rw-r--r--renderer/Cargo.toml18
-rw-r--r--renderer/src/main.rs278
-rw-r--r--renderer/src/map.rs161
-rw-r--r--renderer/src/tee.rs206
-rw-r--r--snapshot/Cargo.toml27
-rw-r--r--snapshot/src/format.rs85
-rw-r--r--snapshot/src/lib.rs36
-rw-r--r--snapshot/src/manager.rs140
-rw-r--r--snapshot/src/receiver.rs245
-rw-r--r--snapshot/src/snap.rs511
-rw-r--r--snapshot/src/storage.rs176
23 files changed, 10 insertions, 2892 deletions
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 <metamuffin@disroot.org>
-Based on MIT licenced code of libtw2 by heinrich5991 <heinrich5991@gmail.com>
+twclient - teeworlds client in rust
+Copyright (C) 2025 metamuffin <metamuffin@disroot.org>
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 <metamuffin@disroot.org>",
- "heinrich5991 <heinrich5991@gmail.com>",
-]
-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<System<'a>>>(&mut self, pid: PeerId, msg: S) {
- fn inner<L: Loop + ?Sized>(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<Game<'a>>>(&mut self, pid: PeerId, msg: G) {
- fn inner<L: Loop + ?Sized>(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<L: Loop> 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<W> 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<W> 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<Vec<u8>>,
- snaps: snapshot::Manager,
- num_snaps_since_reset: u64,
- dummy_map: bool,
- state: ClientState,
- download: Option<Download>,
-
- input: PlayerInput,
-
- interface_receive: Receiver<ClientMesgIn>,
- interface_send: Sender<ClientMesgOut>,
-}
-
-pub struct ClientInterface {
- pub send: Sender<ClientMesgIn>,
- pub receive: Receiver<ClientMesgOut>,
-}
-
-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<L> 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::<Vec<(u16, SnapObj)>>();
-
- 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::<PlayerInput>().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<usize>,
- pub tiles: Array2<Tile>,
- pub kind: LayerTilemapType,
- pub offset: (i32, i32),
-}
-
-pub struct Map {
- pub layers: Vec<Layer>,
- pub tilesets: HashMap<Option<usize>, Array2<Color>>,
- 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<Self, Error> {
- 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::<Color>() != 0 {
- panic!("image shape invalid");
- }
- let data: Vec<Color> = 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<u16, Tee>,
-}
-
-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<const S: usize>(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 <metamuffin@disroot.org>
-Based on MIT licenced code of libtw2 by heinrich5991 <heinrich5991@gmail.com>
-```
+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 <metamuffin@disroot.org>",
- "heinrich5991 <heinrich5991@gmail.com>",
-]
-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<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,
- tee_renderer: TeeRenderer::new(),
- map_renderer: MapRenderer::new(),
- world: World::new(),
- input: PlayerInput::default(),
- };
-
- let mut keys_down = HashSet::<VirtualKeyCode>::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<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
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<usize>, 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<u8> = 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<String, Image>,
-}
-
-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<Image> {
- // 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 <metamuffin@disroot.org>",
- "heinrich5991 <heinrich5991@gmail.com>",
-]
-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<packer::Warning> 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<W: Warn<Warning>>(warn: &mut W, p: &mut Unpacker)
- -> Result<DeltaHeader, Error>
- {
- 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<u32>) -> ops::Range<usize> {
- 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<receiver::Error> for Error {
- fn from(err: receiver::Error) -> Error {
- Error::Receiver(err)
- }
-}
-
-impl From<snap::Error> for Error {
- fn from(err: snap::Error) -> Error {
- Error::Snap(err)
- }
-}
-
-impl From<storage::Error> for Error {
- fn from(err: storage::Error) -> Error {
- Error::Storage(err)
- }
-}
-
-impl From<receiver::Warning> for Warning {
- fn from(w: receiver::Warning) -> Warning {
- Warning::Receiver(w)
- }
-}
-
-impl From<format::Warning> for Warning {
- fn from(w: format::Warning) -> Warning {
- Warning::Snap(w)
- }
-}
-
-impl From<storage::Warning> 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<i32> {
- self.inner.storage.ack_tick()
- }
- pub fn snap_empty<W, O>(&mut self, warn: &mut W, object_size: O, snap: system::SnapEmpty)
- -> Result<Option<&Snap>, Error>
- where W: Warn<Warning>,
- O: FnMut(u16) -> Option<u32>,
- {
- let res = self.receiver.snap_empty(wrap(warn), snap);
- self.inner.handle_msg(warn, object_size, res)
- }
- pub fn snap_single<W, O>(&mut self, warn: &mut W, object_size: O, snap: system::SnapSingle)
- -> Result<Option<&Snap>, Error>
- where W: Warn<Warning>,
- O: FnMut(u16) -> Option<u32>,
- {
- let res = self.receiver.snap_single(wrap(warn), snap);
- self.inner.handle_msg(warn, object_size, res)
- }
- pub fn snap<W, O>(&mut self, warn: &mut W, object_size: O, snap: system::Snap)
- -> Result<Option<&Snap>, Error>
- where W: Warn<Warning>,
- O: FnMut(u16) -> Option<u32>,
- {
- let res = self.receiver.snap(wrap(warn), snap);
- self.inner.handle_msg(warn, object_size, res)
- }
-}
-
-impl ManagerInner {
- fn handle_msg<W, O>(&mut self, warn: &mut W, object_size: O, res: Result<Option<ReceivedDelta>, receiver::Error>)
- -> Result<Option<&Snap>, Error>
- where W: Warn<Warning>,
- O: FnMut(u16) -> Option<u32>,
- {
- Ok(match res? {
- Some(delta) => Some(self.add_delta(warn, object_size, delta)?),
- None => None,
- })
- }
- fn add_delta<W, O>(&mut self, warn: &mut W, object_size: O, delta: ReceivedDelta)
- -> Result<&Snap, Error>
- where W: Warn<Warning>,
- O: FnMut(u16) -> Option<u32>,
- {
- 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<i32>,
- current: Option<CurrentDelta>,
- // `parts` points into `receive_buf`.
- parts: VecMap<ops::Range<u32>>,
- receive_buf: Vec<u8>,
- result: Vec<u8>,
-}
-
-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<W>(&mut self, warn: &mut W, snap: system::SnapEmpty)
- -> Result<Option<ReceivedDelta>, Error>
- where W: Warn<Warning>,
- {
- 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<W>(&mut self, warn: &mut W, snap: system::SnapSingle)
- -> Result<Option<ReceivedDelta>, Error>
- where W: Warn<Warning>,
- {
- 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<W>(&mut self, warn: &mut W, snap: system::Snap)
- -> Result<Option<ReceivedDelta>, Error>
- where W: Warn<Warning>,
- {
- 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<TooLongSnap> for Error {
- fn from(_: TooLongSnap) -> Error {
- Error::TooLongSnap
- }
-}
-
-impl From<TooLongSnap> for BuilderError {
- fn from(_: TooLongSnap) -> BuilderError {
- BuilderError::TooLongSnap
- }
-}
-
-impl From<packer::IntOutOfRange> for Error {
- fn from(_: packer::IntOutOfRange) -> Error {
- Error::IntOutOfRange
- }
-}
-
-impl From<packer::UnexpectedEnd> 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<i32, ops::Range<u32>>,
- buf: Vec<i32>,
-}
-
-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<u32>) -> &[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<u32>>, buf: &mut Vec<i32>, size: usize)
- -> Result<&'a mut ops::Range<u32>, 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<W>(&mut self, warn: &mut W, from: &Snap, delta: &Delta)
- -> Result<(), Error>
- where W: Warn<Warning>,
- {
- 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<i32>, 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::<i32>()).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::<i32>())
- .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<u32>>,
-}
-
-impl<'a> Iterator for Items<'a> {
- type Item = Item<'a>;
- fn next(&mut self) -> Option<Item<'a>> {
- self.iter.next().map(|(&k, o)| {
- Item::from_key(k, self.snap.item_from_offset(o.clone()))
- })
- }
- fn size_hint(&self) -> (usize, Option<usize>) {
- 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<i32>,
- updated_items: HashMap<i32, ops::Range<u32>>,
- buf: Vec<i32>,
-}
-
-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<u32>,
- {
- 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<i32>,
-}
-
-impl DeltaReader {
- pub fn new() -> DeltaReader {
- Default::default()
- }
- fn clear(&mut self) {
- self.buf.clear();
- }
- pub fn read<W, O>(&mut self, warn: &mut W, delta: &mut Delta, object_size: O, p: &mut Unpacker)
- -> Result<(), Error>
- where W: Warn<Warning>,
- O: FnMut(u16) -> Option<u32>,
- {
- 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<system::System<'a>> 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<SnapMsg<'a>> {
- 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<snap::Error> 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<format::Warning> 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<StoredSnap>,
- free: Vec<Snap>,
- ack_tick: Option<i32>,
- delta: Delta,
- delta_tick: Option<i32>,
-}
-
-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<i32> {
- self.ack_tick
- }
- pub fn add_delta<W>(&mut self, warn: &mut W, crc: Option<i32>, delta_tick: i32, tick: i32, delta: &Delta)
- -> Result<&Snap, Error>
- where W: Warn<Warning>,
- {
- 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 = &empty;
- 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<W>(&mut self, warn: &mut W, tick: i32)
- -> Result<(), UnknownSnap>
- where W: Warn<WeirdNegativeDeltaTick>,
- {
- 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<i32> {
- 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
- }
-}