libtw2_demo/ddnet/
writer.rs

1use crate::format;
2use libtw2_common::digest::Sha256;
3use libtw2_common::num::Cast;
4use libtw2_gamenet_common::snap_obj;
5use libtw2_gamenet_common::traits::MessageExt as _;
6use libtw2_gamenet_common::traits::Protocol;
7use libtw2_gamenet_common::traits::SnapObj as _;
8use libtw2_packer::with_packer;
9use libtw2_snapshot::snap;
10use libtw2_snapshot::Delta;
11use libtw2_snapshot::Snap;
12use std::convert::TryInto;
13use std::io;
14use std::marker::PhantomData;
15use std::mem;
16use thiserror::Error;
17
18#[derive(Debug, Error)]
19pub enum WriteError {
20    #[error(transparent)]
21    Inner(crate::WriteError),
22    #[error("Snap creation - {0:?}")]
23    SnapBuilder(snap::BuilderError),
24    #[error("Tick decreased or is negative")]
25    TooLowTickNumber,
26    #[error("Snap data does not fit into buffer")]
27    TooLargeSnap,
28    #[error("Net message data does not fit into buffer")]
29    TooLongNetMsg,
30}
31
32impl From<crate::WriteError> for WriteError {
33    fn from(value: crate::WriteError) -> Self {
34        Self::Inner(value)
35    }
36}
37
38impl From<snap::BuilderError> for WriteError {
39    fn from(value: snap::BuilderError) -> Self {
40        Self::SnapBuilder(value)
41    }
42}
43
44/// DDNet demo writer.
45///
46/// Automatically writes snapshot deltas.
47pub struct DemoWriter<P: for<'a> Protocol<'a>> {
48    inner: crate::Writer,
49    // To verify the monotonic increase
50    last_tick: i32,
51    // Stores the last tick, in which a snapshot was written.
52    last_keyframe: Option<i32>,
53    uuid_index: UuidIndex,
54    snap: Snap,
55    builder: snap::Builder,
56    delta: Delta,
57    buf: arrayvec::ArrayVec<[u8; format::MAX_SNAPSHOT_SIZE]>,
58    i32_buf: Vec<i32>,
59    protocol: PhantomData<P>,
60}
61
62#[derive(Default)]
63struct UuidIndex(Vec<[u8; 16]>);
64
65impl UuidIndex {
66    fn index_to_type_id(index: u16) -> u16 {
67        // 0x7fff is the maximum type id
68        0x7fff - index
69    }
70
71    /// The uuid items need to be inserted into every snap.
72    /// Not only in the first one where they appear.
73    /// This function should be called inbetween each snap.
74    fn write_to_snap(&mut self, builder: &mut snap::Builder) -> Result<(), snap::BuilderError> {
75        for (index, uuid) in self.0.iter().enumerate() {
76            let type_id = Self::index_to_type_id(index.assert_u16());
77            let mut uuid_item_ints = [0; 4];
78            for (uuid_int, uuid_bytes) in uuid_item_ints.iter_mut().zip(uuid.chunks(4)) {
79                *uuid_int = i32::from_be_bytes(uuid_bytes.try_into().unwrap());
80            }
81            builder.add_item(0, type_id, &uuid_item_ints)?;
82        }
83        Ok(())
84    }
85
86    fn get_type_id(&mut self, uuid: &[u8; 16]) -> u16 {
87        if let Some(index) = self.0.iter().position(|e| e == uuid) {
88            Self::index_to_type_id(index.assert_u16())
89        } else {
90            let new_index = self.0.len().assert_u16();
91            self.0.push(*uuid);
92            Self::index_to_type_id(new_index)
93        }
94    }
95}
96
97impl<P: for<'a> Protocol<'a>> DemoWriter<P> {
98    pub fn new<T: io::Write + io::Seek + 'static>(
99        file: T,
100        net_version: &[u8],
101        map_name: &[u8],
102        map_sha256: Option<Sha256>,
103        map_crc: u32,
104        kind: crate::DemoKind,
105        length: i32,
106        timestamp: &[u8],
107        map: &[u8],
108    ) -> Result<Self, WriteError> {
109        let raw = crate::Writer::new(
110            file,
111            net_version,
112            map_name,
113            map_sha256,
114            map_crc,
115            kind,
116            length,
117            timestamp,
118            map,
119        )?;
120
121        Ok(Self {
122            inner: raw,
123            last_tick: -1,
124            last_keyframe: None,
125            uuid_index: UuidIndex::default(),
126            snap: Snap::default(),
127            delta: Delta::default(),
128            builder: snap::Builder::default(),
129            buf: arrayvec::ArrayVec::new(),
130            i32_buf: Vec::new(),
131            protocol: PhantomData,
132        })
133    }
134
135    pub fn write_snap<'a, T: Iterator<Item = (&'a P::SnapObj, u16)>>(
136        &mut self,
137        tick: i32,
138        items: T,
139    ) -> Result<(), WriteError> {
140        // Verify that the tick number is strictly increasing.
141        if tick < self.last_tick {
142            return Err(WriteError::TooLowTickNumber);
143        }
144        // We write a keyframe at the start, and another keyframe every 5 seconds.
145        // We assume a tickrate of 50 ticks per second.
146        let is_keyframe = match self.last_keyframe {
147            None => true,
148            Some(last_keyframe) => tick - last_keyframe > 250,
149        };
150
151        // Build snap with UUID items, which map the UUIDs of ex-items to type ids.
152        for (item, id) in items {
153            let type_id = match item.obj_type_id() {
154                snap_obj::TypeId::Ordinal(type_id) => type_id,
155                snap_obj::TypeId::Uuid(uuid) => self.uuid_index.get_type_id(uuid.as_bytes()),
156            };
157            self.builder.add_item(type_id, id, item.encode())?;
158        }
159        self.uuid_index.write_to_snap(&mut self.builder)?;
160
161        let old_snap = mem::take(&mut self.snap);
162        let new_snap = mem::take(&mut self.builder).finish();
163
164        self.inner.write_tick(is_keyframe, tick)?;
165        if is_keyframe {
166            let keys = &mut self.i32_buf;
167            with_packer(&mut self.buf, |p| new_snap.write(keys, p))
168                .map_err(|_| WriteError::TooLargeSnap)?;
169            self.inner.write_snapshot(&self.buf)?;
170        } else {
171            self.delta.create(&old_snap, &new_snap);
172            let delta = &self.delta;
173            with_packer(&mut self.buf, |p| delta.write(P::obj_size, p))
174                .map_err(|_| WriteError::TooLargeSnap)?;
175            self.inner.write_snapshot_delta(&self.buf)?;
176        }
177
178        // Snap deltas always rely on the snap of the last tick in the demo.
179        // They don't rely on the last keyframe.
180        // For that, we always need to store the newest snap.
181        self.snap = new_snap;
182        self.builder = old_snap.recycle();
183        self.buf.clear();
184        self.last_tick = tick;
185        if is_keyframe {
186            self.last_keyframe = Some(tick);
187        }
188        Ok(())
189    }
190    pub fn write_msg(&mut self, msg: &<P as Protocol<'_>>::Game) -> Result<(), WriteError> {
191        with_packer(&mut self.buf, |p| msg.encode(p)).map_err(|_| WriteError::TooLongNetMsg)?;
192        self.inner.write_message(self.buf.as_slice())?;
193        self.buf.clear();
194        Ok(())
195    }
196}