Skip to content

Commit e4791aa

Browse files
authored
Add ValidatedBlockPosition (#483)
* Add `ValidBlockPosition` * Update feather internals to use `ValidBlockPosition` * Apply the automated cargo fixes because I'm lazy as HECK * Use new functions in place of old hack * Add tests for good measure * Remove wildcard dependency on `bytemuck`
1 parent cf650cc commit e4791aa

File tree

16 files changed

+268
-68
lines changed

16 files changed

+268
-68
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

feather/base/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ smallvec = "1"
3232
thiserror = "1"
3333
uuid = { version = "0.8", features = [ "serde" ] }
3434
vek = "0.14"
35+
bytemuck = { version = "1", features = ["derive"] }
3536

3637
[dev-dependencies]
3738
rand = "0.8"

feather/base/src/block.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
use bytemuck::{Pod, Zeroable};
2+
use serde::{Deserialize, Serialize};
3+
4+
use thiserror::Error;
5+
6+
use libcraft_core::{BlockPosition, ChunkPosition, Position};
7+
use std::convert::TryFrom;
8+
9+
/// Validated position of a block.
10+
///
11+
/// This structure is immutable.
12+
/// All operations that change a [`ValidBlockPosition`] must be done by
13+
/// turning it into a [`BlockPosition`], performing said operations,
14+
/// then using [`ValidBlockPosition`]'s [`TryFrom`] impl to get a [`ValidBlockPosition`].
15+
///
16+
/// The definition of a valid block position is defined by [`BlockPosition::valid`].
17+
///
18+
/// # Examples
19+
///
20+
/// Converting a [`BlockPosition`] to a [`ValidBlockPosition`], unwrapping any errors that
21+
/// occur.
22+
/// ```
23+
/// # use feather_base::BlockPosition;
24+
/// # use feather_base::ValidBlockPosition;
25+
/// # use std::convert::TryInto;
26+
/// // Create an unvalidated block position
27+
/// let block_position = BlockPosition::new(727, 32, 727);
28+
///
29+
/// // Validate the block position and unwrap any errors
30+
/// let valid_block_position: ValidBlockPosition = block_position.try_into().unwrap();
31+
/// ```
32+
///
33+
/// Performing operations on a [`ValidBlockPosition`], then re-validating it.
34+
/// ```
35+
/// # use feather_base::BlockPosition;
36+
/// # use feather_base::ValidBlockPosition;
37+
/// # use std::convert::TryInto;
38+
/// # let mut valid_block_position: ValidBlockPosition = BlockPosition::new(727, 32, 727).try_into().unwrap();
39+
/// // Convert the ValidBlockPosition into an unvalidated one to perform math
40+
/// let mut block_position: BlockPosition = valid_block_position.into();
41+
///
42+
/// block_position.x = 821;
43+
/// block_position.z += 32;
44+
///
45+
/// assert!(block_position.valid());
46+
///
47+
/// valid_block_position = block_position.try_into().unwrap();
48+
/// ```
49+
#[derive(
50+
Clone,
51+
Copy,
52+
Debug,
53+
PartialEq,
54+
Eq,
55+
Hash,
56+
PartialOrd,
57+
Ord,
58+
Default,
59+
Serialize,
60+
Deserialize,
61+
Zeroable,
62+
Pod,
63+
)]
64+
#[repr(C)]
65+
pub struct ValidBlockPosition {
66+
x: i32,
67+
y: i32,
68+
z: i32,
69+
}
70+
71+
impl ValidBlockPosition {
72+
pub fn x(&self) -> i32 {
73+
self.x
74+
}
75+
76+
pub fn y(&self) -> i32 {
77+
self.y
78+
}
79+
80+
pub fn z(&self) -> i32 {
81+
self.z
82+
}
83+
84+
pub fn chunk(self) -> ChunkPosition {
85+
self.into()
86+
}
87+
88+
pub fn position(self) -> Position {
89+
self.into()
90+
}
91+
}
92+
93+
impl TryFrom<BlockPosition> for ValidBlockPosition {
94+
type Error = BlockPositionValidationError;
95+
96+
fn try_from(value: BlockPosition) -> Result<Self, Self::Error> {
97+
if value.valid() {
98+
Ok(ValidBlockPosition {
99+
x: value.x,
100+
y: value.y,
101+
z: value.z,
102+
})
103+
} else {
104+
Err(BlockPositionValidationError::OutOfRange(value))
105+
}
106+
}
107+
}
108+
109+
impl From<ValidBlockPosition> for BlockPosition {
110+
fn from(position: ValidBlockPosition) -> Self {
111+
BlockPosition {
112+
x: position.x,
113+
y: position.y,
114+
z: position.z,
115+
}
116+
}
117+
}
118+
119+
impl From<ValidBlockPosition> for ChunkPosition {
120+
fn from(position: ValidBlockPosition) -> Self {
121+
let position: BlockPosition = position.into();
122+
position.into()
123+
}
124+
}
125+
126+
impl From<ValidBlockPosition> for Position {
127+
fn from(position: ValidBlockPosition) -> Self {
128+
let position: BlockPosition = position.into();
129+
position.into()
130+
}
131+
}
132+
133+
#[derive(Error, Debug)]
134+
pub enum BlockPositionValidationError {
135+
#[error("coordinate {0:?} out of range")]
136+
OutOfRange(BlockPosition),
137+
}
138+
139+
#[cfg(test)]
140+
mod tests {
141+
142+
use std::convert::TryInto;
143+
144+
use libcraft_core::BlockPosition;
145+
146+
use crate::ValidBlockPosition;
147+
148+
#[test]
149+
#[should_panic]
150+
fn check_out_of_bounds_up() {
151+
let block_position = BlockPosition::new(0, 39483298, 0);
152+
153+
<BlockPosition as TryInto<ValidBlockPosition>>::try_into(block_position).unwrap();
154+
}
155+
156+
#[test]
157+
#[should_panic]
158+
fn check_out_of_bounds_down() {
159+
let block_position = BlockPosition::new(0, -39483298, 0);
160+
161+
<BlockPosition as TryInto<ValidBlockPosition>>::try_into(block_position).unwrap();
162+
}
163+
}

feather/base/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ use num_derive::{FromPrimitive, ToPrimitive};
1010
use serde::{Deserialize, Serialize};
1111

1212
pub mod anvil;
13+
mod block;
1314
pub mod chunk;
1415
pub mod chunk_lock;
1516
pub mod inventory;
1617
pub mod metadata;
1718

19+
pub use block::{BlockPositionValidationError, ValidBlockPosition};
1820
pub use blocks::*;
1921
pub use chunk::{Chunk, ChunkSection, CHUNK_HEIGHT, CHUNK_WIDTH};
2022
pub use chunk_lock::*;

feather/base/src/metadata.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! metadata format. See <https://wiki.vg/Entity_metadata>
33
//! for the specification.
44
5-
use crate::{BlockPosition, Direction};
5+
use crate::{Direction, ValidBlockPosition};
66
use bitflags::bitflags;
77
use generated::ItemStack;
88
use std::collections::BTreeMap;
@@ -47,8 +47,8 @@ pub enum MetaEntry {
4747
Slot(Option<ItemStack>),
4848
Boolean(bool),
4949
Rotation(f32, f32, f32),
50-
Position(BlockPosition),
51-
OptPosition(Option<BlockPosition>),
50+
Position(ValidBlockPosition),
51+
OptPosition(Option<ValidBlockPosition>),
5252
Direction(Direction),
5353
OptUuid(OptUuid),
5454
OptBlockId(Option<i32>),
@@ -131,7 +131,7 @@ impl ToMetaEntry for bool {
131131
}
132132
}
133133

134-
impl ToMetaEntry for BlockPosition {
134+
impl ToMetaEntry for ValidBlockPosition {
135135
fn to_meta_entry(&self) -> MetaEntry {
136136
MetaEntry::Position(*self)
137137
}

feather/common/src/events/block_change.rs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use std::iter;
1+
use std::{convert::TryInto, iter};
22

33
use base::{
44
chunk::{SECTION_HEIGHT, SECTION_VOLUME},
5-
BlockPosition, ChunkPosition,
5+
BlockPosition, ChunkPosition, ValidBlockPosition,
66
};
77
use itertools::Either;
88

@@ -18,7 +18,7 @@ pub struct BlockChangeEvent {
1818

1919
impl BlockChangeEvent {
2020
/// Creates an event affecting a single block.
21-
pub fn single(pos: BlockPosition) -> Self {
21+
pub fn single(pos: ValidBlockPosition) -> Self {
2222
Self {
2323
changes: BlockChanges::Single { pos },
2424
}
@@ -42,7 +42,7 @@ impl BlockChangeEvent {
4242
}
4343

4444
/// Returns an iterator over block positions affected by this block change.
45-
pub fn iter_changed_blocks(&self) -> impl Iterator<Item = BlockPosition> + '_ {
45+
pub fn iter_changed_blocks(&self) -> impl Iterator<Item = ValidBlockPosition> + '_ {
4646
match &self.changes {
4747
BlockChanges::Single { pos } => Either::Left(iter::once(*pos)),
4848
BlockChanges::FillChunkSection { chunk, section } => {
@@ -60,7 +60,7 @@ impl BlockChangeEvent {
6060
) -> impl Iterator<Item = (ChunkPosition, usize, usize)> + '_ {
6161
match &self.changes {
6262
BlockChanges::Single { pos } => {
63-
iter::once((pos.chunk(), pos.y as usize / SECTION_HEIGHT, 1))
63+
iter::once((pos.chunk(), pos.y() as usize / SECTION_HEIGHT, 1))
6464
}
6565
BlockChanges::FillChunkSection { chunk, section } => {
6666
iter::once((*chunk, *section as usize, SECTION_VOLUME))
@@ -69,22 +69,27 @@ impl BlockChangeEvent {
6969
}
7070
}
7171

72-
fn iter_section_blocks(chunk: ChunkPosition, section: u32) -> impl Iterator<Item = BlockPosition> {
72+
fn iter_section_blocks(
73+
chunk: ChunkPosition,
74+
section: u32,
75+
) -> impl Iterator<Item = ValidBlockPosition> {
7376
(0..16)
7477
.flat_map(|x| (0..16).map(move |y| (x, y)))
7578
.flat_map(|(x, y)| (0..16).map(move |z| (x, y, z)))
7679
.map(move |(dx, dy, dz)| {
7780
let x = dx + chunk.x * 16;
7881
let y = dy + section as i32 * 16;
7982
let z = dz + chunk.z * 16;
80-
BlockPosition::new(x, y, z)
83+
84+
// It's safe to unwrap because we are working from a valid source of block positions
85+
BlockPosition::new(x, y, z).try_into().unwrap()
8186
})
8287
}
8388

8489
#[derive(Debug, Clone)]
8590
enum BlockChanges {
8691
/// A single block change.
87-
Single { pos: BlockPosition },
92+
Single { pos: ValidBlockPosition },
8893
/// A whole chunk section was filled with the same block.
8994
FillChunkSection { chunk: ChunkPosition, section: u32 },
9095
}
@@ -98,7 +103,7 @@ mod tests {
98103

99104
#[test]
100105
fn create_single() {
101-
let pos = BlockPosition::new(5, 64, 9);
106+
let pos = BlockPosition::new(5, 64, 9).try_into().unwrap();
102107
let event = BlockChangeEvent::single(pos);
103108
assert_eq!(event.count(), 1);
104109
assert_eq!(event.iter_changed_blocks().collect::<Vec<_>>(), vec![pos]);
@@ -123,9 +128,9 @@ mod tests {
123128

124129
#[test]
125130
fn test_iter_section_blocks() {
126-
let blocks: Vec<BlockPosition> =
131+
let blocks: Vec<ValidBlockPosition> =
127132
iter_section_blocks(ChunkPosition::new(-1, -2), 5).collect();
128-
let unique_blocks: AHashSet<BlockPosition> = blocks.iter().copied().collect();
133+
let unique_blocks: AHashSet<ValidBlockPosition> = blocks.iter().copied().collect();
129134

130135
assert_eq!(blocks.len(), unique_blocks.len());
131136
assert_eq!(blocks.len(), SECTION_VOLUME);
@@ -134,7 +139,7 @@ mod tests {
134139
for y in 80..96 {
135140
for z in -32..-16 {
136141
assert!(
137-
unique_blocks.contains(&BlockPosition::new(x, y, z)),
142+
unique_blocks.contains(&BlockPosition::new(x, y, z).try_into().unwrap()),
138143
"{}, {}, {}",
139144
x,
140145
y,

feather/common/src/game.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{cell::RefCell, mem, rc::Rc, sync::Arc};
22

3-
use base::{BlockId, BlockPosition, ChunkPosition, Position, Text, Title};
3+
use base::{BlockId, ChunkPosition, Position, Text, Title, ValidBlockPosition};
44
use ecs::{
55
Ecs, Entity, EntityBuilder, HasEcs, HasResources, NoSuchEntity, Resources, SysResult,
66
SystemExecutor,
@@ -181,14 +181,14 @@ impl Game {
181181
}
182182

183183
/// Gets the block at the given position.
184-
pub fn block(&self, pos: BlockPosition) -> Option<BlockId> {
184+
pub fn block(&self, pos: ValidBlockPosition) -> Option<BlockId> {
185185
self.world.block_at(pos)
186186
}
187187

188188
/// Sets the block at the given position.
189189
///
190190
/// Triggers necessary `BlockChangeEvent`s.
191-
pub fn set_block(&mut self, pos: BlockPosition, block: BlockId) -> bool {
191+
pub fn set_block(&mut self, pos: ValidBlockPosition, block: BlockId) -> bool {
192192
let was_successful = self.world.set_block_at(pos, block);
193193
if was_successful {
194194
self.ecs.insert_event(BlockChangeEvent::single(pos));
@@ -226,7 +226,7 @@ impl Game {
226226

227227
/// Breaks the block at the given position, propagating any
228228
/// necessary block updates.
229-
pub fn break_block(&mut self, pos: BlockPosition) -> bool {
229+
pub fn break_block(&mut self, pos: ValidBlockPosition) -> bool {
230230
self.set_block(pos, BlockId::air())
231231
}
232232
}

0 commit comments

Comments
 (0)