From 4b155f4c9bb62e22e74eb6341bf567e4898a60ae Mon Sep 17 00:00:00 2001 From: Ryan Johnson Date: Mon, 14 Nov 2022 01:01:28 -0800 Subject: [PATCH] Expand on the "encoded buf" idea in valence_protocol (#155) Adds a `Cached` type and renames some things. --- valence_protocol/src/cache.rs | 143 ++++++++++++++++++++++++++++ valence_protocol/src/encoded_buf.rs | 47 --------- valence_protocol/src/lib.rs | 2 +- 3 files changed, 144 insertions(+), 48 deletions(-) create mode 100644 valence_protocol/src/cache.rs delete mode 100644 valence_protocol/src/encoded_buf.rs diff --git a/valence_protocol/src/cache.rs b/valence_protocol/src/cache.rs new file mode 100644 index 0000000..04bc1a2 --- /dev/null +++ b/valence_protocol/src/cache.rs @@ -0,0 +1,143 @@ +use std::io::Write; +use std::marker::PhantomData; +use std::mem; + +use anyhow::anyhow; + +use crate::{Decode, Encode, Result}; + +/// Contains both a value of `T` and an [`EncodedBuf`] to ensure the +/// buffer is updated when `T` is modified[^note]. +/// +/// The `Encode` implementation for `Cached` encodes only the contained +/// `EncodedBuf`. +/// +/// Use this type when you want `Encode` to be cached but you also need to read +/// from the value of `T`. If the value of `T` is write-only, consider using +/// `EncodedBuf` instead. +/// +/// [`EncodedBuf`]: EncodedBuf +/// [^note]: Assuming `T` does not use internal mutability. +pub struct Cached { + val: T, + buf: EncodedBuf, +} + +impl Cached { + pub fn new(val: T) -> Self { + let buf = EncodedBuf::new(&val); + + Self { val, buf } + } + + pub fn get(&self) -> &T { + &self.val + } + + pub fn buf(&self) -> &EncodedBuf { + &self.buf + } + + /// Provides a mutable reference to the contained `T` for modification. The + /// buffer is re-encoded when the closure returns. + pub fn modify(&mut self, f: impl FnOnce(&mut T) -> U) -> U { + let u = f(&mut self.val); + self.buf.set(&self.val); + u + } + + pub fn replace(&mut self, new: T) -> T { + self.modify(|old| mem::replace(old, new)) + } + + pub fn into_inner(self) -> (T, EncodedBuf) { + (self.val, self.buf) + } +} + +impl Encode for Cached +where + T: Encode, +{ + fn encode(&self, w: impl Write) -> Result<()> { + self.buf.encode(w) + } +} + +/// The `Decode` implementation for `Cached` exists for the sake of +/// completeness, but you probably shouldn't need to use it. +impl<'a, T> Decode<'a> for Cached +where + T: Encode + Decode<'a>, +{ + fn decode(r: &mut &'a [u8]) -> Result { + let val = T::decode(r)?; + Ok(Self::new(val)) + } +} + +/// Caches the result of `T`'s [`Encode`] implementation into an owned buffer. +/// +/// This is useful for types with expensive [`Encode`] implementations such as +/// [`Text`] or [`Compound`]. It has little to no benefit for primitive types +/// such as `i32`, `VarInt`, `&str`, `&[u8]`, etc. +/// +/// # Examples +/// +/// ``` +/// use valence_protocol::cache::EncodedBuf; +/// use valence_protocol::Encode; +/// +/// let mut buf1 = Vec::new(); +/// let mut buf2 = Vec::new(); +/// +/// "hello".encode(&mut buf1).unwrap(); +/// +/// let cache = EncodedBuf::new("hello"); +/// cache.encode(&mut buf2).unwrap(); +/// +/// assert_eq!(buf1, buf2); +/// ``` +/// +/// [`Text`]: crate::text::Text +/// [`Compound`]: valence_nbt::Compound +pub struct EncodedBuf { + buf: Vec, + res: Result<()>, + _marker: PhantomData T>, +} + +impl EncodedBuf { + pub fn new(t: &T) -> Self { + let mut buf = Vec::new(); + let res = t.encode(&mut buf); + + Self { + buf, + res, + _marker: PhantomData, + } + } + + pub fn set(&mut self, t: &T) { + self.buf.clear(); + self.res = t.encode(&mut self.buf); + } + + pub fn into_inner(self) -> Result> { + self.res.map(|()| self.buf) + } +} + +impl Encode for EncodedBuf { + fn encode(&self, mut w: impl Write) -> Result<()> { + match &self.res { + Ok(()) => Ok(w.write_all(&self.buf)?), + Err(e) => Err(anyhow!("{e:#}")), + } + } + + fn encoded_len(&self) -> usize { + self.buf.len() + } +} diff --git a/valence_protocol/src/encoded_buf.rs b/valence_protocol/src/encoded_buf.rs deleted file mode 100644 index 34d2778..0000000 --- a/valence_protocol/src/encoded_buf.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::io::Write; -use std::marker::PhantomData; - -use anyhow::anyhow; - -use crate::{Encode, Result}; - -pub struct CachedEncode { - buf: Vec, - res: Result<()>, - _marker: PhantomData T>, -} - -impl CachedEncode { - pub fn new(t: &T) -> Self { - let mut buf = Vec::new(); - let res = t.encode(&mut buf); - - Self { - buf, - res, - _marker: PhantomData, - } - } - - pub fn set(&mut self, t: &T) { - self.buf.clear(); - self.res = t.encode(&mut self.buf); - } - - pub fn into_inner(self) -> Result> { - self.res.map(|()| self.buf) - } -} - -impl Encode for CachedEncode { - fn encode(&self, mut w: impl Write) -> Result<()> { - match &self.res { - Ok(()) => Ok(w.write_all(&self.buf)?), - Err(e) => Err(anyhow!("{e:#}")), - } - } - - fn encoded_len(&self) -> usize { - self.buf.len() - } -} diff --git a/valence_protocol/src/lib.rs b/valence_protocol/src/lib.rs index 9756d7b..11cc9ae 100644 --- a/valence_protocol/src/lib.rs +++ b/valence_protocol/src/lib.rs @@ -91,9 +91,9 @@ pub mod block_pos; pub mod bounded; pub mod byte_angle; mod byte_counter; +pub mod cache; pub mod codec; pub mod enchant; -pub mod encoded_buf; pub mod entity_meta; pub mod ident; mod impls;