Skip to main content

steel_registry/
position_source.rs

1use std::fmt::{self, Debug, Formatter};
2use std::io::{Cursor, Error, Result, Write};
3
4use rustc_hash::FxHashMap;
5use steel_utils::codec::VarInt;
6use steel_utils::serial::{ReadFrom, WriteTo};
7use steel_utils::{BlockPos, Downcast as _, DowncastType, DowncastTypeKey, ErasedType, Identifier};
8
9use crate::{REGISTRY, RegistryExt};
10
11/// Concrete network payload behavior for a registered position-source type.
12pub trait PositionSourceCodec:
13    DowncastType + Clone + Debug + PartialEq + Send + Sync + 'static
14{
15    fn read_network(data: &mut Cursor<&[u8]>) -> Result<Self>;
16    fn write_network(&self, writer: &mut Vec<u8>) -> Result<()>;
17}
18
19trait ErasedPositionSource: ErasedType + Debug + Send + Sync {
20    fn clone_source(&self) -> Box<dyn ErasedPositionSource>;
21    fn source_eq(&self, other: &dyn ErasedPositionSource) -> bool;
22}
23
24impl<T: PositionSourceCodec> ErasedPositionSource for T {
25    fn clone_source(&self) -> Box<dyn ErasedPositionSource> {
26        Box::new(self.clone())
27    }
28
29    fn source_eq(&self, other: &dyn ErasedPositionSource) -> bool {
30        other.downcast_ref::<T>() == Some(self)
31    }
32}
33
34type NetworkReader = fn(&mut Cursor<&[u8]>) -> Result<Box<dyn ErasedPositionSource>>;
35type NetworkWriter = fn(&dyn ErasedPositionSource, &mut Vec<u8>) -> Result<()>;
36
37/// A registered position-source discriminator and its network codec.
38pub struct PositionSourceType {
39    pub key: Identifier,
40    expected_type_key: DowncastTypeKey,
41    network_reader: NetworkReader,
42    network_writer: NetworkWriter,
43}
44
45impl PositionSourceType {
46    #[must_use]
47    pub const fn of<T: PositionSourceCodec>(key: Identifier) -> Self {
48        Self {
49            key,
50            expected_type_key: T::TYPE_KEY,
51            network_reader: read_network::<T>,
52            network_writer: write_network::<T>,
53        }
54    }
55}
56
57impl Debug for PositionSourceType {
58    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
59        formatter
60            .debug_struct("PositionSourceType")
61            .field("key", &self.key)
62            .field("expected_type_key", &self.expected_type_key)
63            .finish_non_exhaustive()
64    }
65}
66
67pub type PositionSourceTypeRef = &'static PositionSourceType;
68
69/// A registry-dispatched position source suitable for a particle network payload.
70pub struct PositionSource {
71    source_type: PositionSourceTypeRef,
72    value: Box<dyn ErasedPositionSource>,
73}
74
75impl PositionSource {
76    #[must_use]
77    pub fn new<T: PositionSourceCodec>(source_type: PositionSourceTypeRef, value: T) -> Self {
78        assert_eq!(
79            source_type.expected_type_key,
80            T::TYPE_KEY,
81            "position source value does not match its registered type"
82        );
83        Self {
84            source_type,
85            value: Box::new(value),
86        }
87    }
88
89    #[must_use]
90    pub const fn source_type(&self) -> PositionSourceTypeRef {
91        self.source_type
92    }
93
94    #[must_use]
95    pub fn downcast_ref<T: DowncastType>(&self) -> Option<&T> {
96        self.value.downcast_ref::<T>()
97    }
98}
99
100impl Clone for PositionSource {
101    fn clone(&self) -> Self {
102        Self {
103            source_type: self.source_type,
104            value: self.value.clone_source(),
105        }
106    }
107}
108
109impl Debug for PositionSource {
110    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
111        formatter
112            .debug_struct("PositionSource")
113            .field("source_type", &self.source_type.key)
114            .field("value", &self.value)
115            .finish()
116    }
117}
118
119impl PartialEq for PositionSource {
120    fn eq(&self, other: &Self) -> bool {
121        self.source_type.key == other.source_type.key && self.value.source_eq(other.value.as_ref())
122    }
123}
124
125impl WriteTo for PositionSource {
126    fn write(&self, writer: &mut impl Write) -> Result<()> {
127        let (id, source_type) = REGISTRY
128            .position_source_types
129            .registered_entry_with_id(self.source_type)
130            .ok_or_else(|| {
131                Error::other(format!(
132                    "Position source type is not the registered value for key: {}",
133                    self.source_type.key
134                ))
135            })?;
136        let id = i32::try_from(id)
137            .map_err(|_| Error::other(format!("Position source type id out of range: {id}")))?;
138        VarInt(id).write(writer)?;
139
140        let mut payload = Vec::new();
141        (source_type.network_writer)(self.value.as_ref(), &mut payload)?;
142        writer.write_all(&payload)
143    }
144}
145
146impl ReadFrom for PositionSource {
147    fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
148        let id = VarInt::read(data)?.0;
149        let id = usize::try_from(id)
150            .map_err(|_| Error::other(format!("Negative position source type id: {id}")))?;
151        let source_type = REGISTRY
152            .position_source_types
153            .by_id(id)
154            .ok_or_else(|| Error::other(format!("Unknown position source type id: {id}")))?;
155        let value = (source_type.network_reader)(data)?;
156        Ok(Self { source_type, value })
157    }
158}
159
160pub struct PositionSourceTypeRegistry {
161    types_by_id: Vec<PositionSourceTypeRef>,
162    types_by_key: FxHashMap<Identifier, usize>,
163    allows_registering: bool,
164}
165
166impl PositionSourceTypeRegistry {
167    #[must_use]
168    pub fn new() -> Self {
169        Self {
170            types_by_id: Vec::new(),
171            types_by_key: FxHashMap::default(),
172            allows_registering: true,
173        }
174    }
175
176    fn registered_entry_with_id(
177        &self,
178        entry: PositionSourceTypeRef,
179    ) -> Option<(usize, PositionSourceTypeRef)> {
180        let id = self.types_by_key.get(&entry.key).copied()?;
181        let registered = self.types_by_id.get(id).copied()?;
182        std::ptr::eq(registered, entry).then_some((id, registered))
183    }
184}
185
186crate::impl_standard_methods!(
187    PositionSourceTypeRegistry,
188    PositionSourceTypeRef,
189    types_by_id,
190    types_by_key,
191    allows_registering,
192    "Cannot register duplicate position source type key: {}"
193);
194crate::impl_registry!(
195    PositionSourceTypeRegistry,
196    PositionSourceType,
197    types_by_id,
198    types_by_key,
199    position_source_types
200);
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub struct BlockPositionSource {
204    pos: BlockPos,
205}
206
207impl BlockPositionSource {
208    #[must_use]
209    pub const fn new(pos: BlockPos) -> Self {
210        Self { pos }
211    }
212
213    #[must_use]
214    pub const fn pos(&self) -> BlockPos {
215        self.pos
216    }
217}
218
219// SAFETY: This Steel-owned key uniquely identifies the concrete position-source payload.
220unsafe impl DowncastType for BlockPositionSource {
221    const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:position_source/block");
222}
223
224impl PositionSourceCodec for BlockPositionSource {
225    fn read_network(data: &mut Cursor<&[u8]>) -> Result<Self> {
226        Ok(Self::new(BlockPos::read(data)?))
227    }
228
229    fn write_network(&self, writer: &mut Vec<u8>) -> Result<()> {
230        self.pos.write(writer)
231    }
232}
233
234#[derive(Debug, Clone, Copy, PartialEq)]
235pub struct EntityPositionSource {
236    entity_id: i32,
237    y_offset: f32,
238}
239
240impl EntityPositionSource {
241    #[must_use]
242    pub const fn new(entity_id: i32, y_offset: f32) -> Self {
243        Self {
244            entity_id,
245            y_offset,
246        }
247    }
248
249    #[must_use]
250    pub const fn entity_id(&self) -> i32 {
251        self.entity_id
252    }
253
254    #[must_use]
255    pub const fn y_offset(&self) -> f32 {
256        self.y_offset
257    }
258}
259
260// SAFETY: This Steel-owned key uniquely identifies the concrete position-source payload.
261unsafe impl DowncastType for EntityPositionSource {
262    const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:position_source/entity");
263}
264
265impl PositionSourceCodec for EntityPositionSource {
266    fn read_network(data: &mut Cursor<&[u8]>) -> Result<Self> {
267        Ok(Self::new(VarInt::read(data)?.0, f32::read(data)?))
268    }
269
270    fn write_network(&self, writer: &mut Vec<u8>) -> Result<()> {
271        VarInt(self.entity_id).write(writer)?;
272        self.y_offset.write(writer)
273    }
274}
275
276fn read_network<T: PositionSourceCodec>(
277    data: &mut Cursor<&[u8]>,
278) -> Result<Box<dyn ErasedPositionSource>> {
279    Ok(Box::new(T::read_network(data)?))
280}
281
282fn write_network<T: PositionSourceCodec>(
283    value: &dyn ErasedPositionSource,
284    writer: &mut Vec<u8>,
285) -> Result<()> {
286    let value = value.downcast_ref::<T>().ok_or_else(|| {
287        Error::other(format!(
288            "Position source payload does not match {}",
289            T::TYPE_KEY
290        ))
291    })?;
292    value.write_network(writer)
293}
294
295#[cfg(test)]
296mod tests {
297    use steel_utils::Identifier;
298    use steel_utils::serial::WriteTo;
299
300    use crate::{init_vanilla_registry, vanilla_position_source_types};
301
302    use super::{
303        EntityPositionSource, PositionSource, PositionSourceType, PositionSourceTypeRegistry,
304    };
305
306    static FORGED_BLOCK_SOURCE: PositionSourceType =
307        PositionSourceType::of::<EntityPositionSource>(Identifier::vanilla_static("block"));
308
309    #[test]
310    fn position_source_write_rejects_noncanonical_same_key_codec() {
311        init_vanilla_registry();
312
313        let source =
314            PositionSource::new(&FORGED_BLOCK_SOURCE, EntityPositionSource::new(1234, 1.5));
315        let mut encoded = Vec::new();
316        let result = source.write(&mut encoded);
317
318        assert!(result.is_err());
319        assert_eq!(encoded.len(), 0);
320    }
321
322    #[test]
323    #[should_panic(expected = "Cannot register duplicate position source type key")]
324    fn position_source_type_registry_rejects_duplicate_keys() {
325        let mut registry = PositionSourceTypeRegistry::new();
326        registry.register(&vanilla_position_source_types::BLOCK);
327        registry.register(&FORGED_BLOCK_SOURCE);
328    }
329}