Skip to content

Commit

Permalink
block: Make BlockAttributes #[non_exhaustive].
Browse files Browse the repository at this point in the history
To construct an instance, `block::Builder()` can now build attributes.
  • Loading branch information
kpreid committed Jan 19, 2025
1 parent be82f4d commit 2f06158
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 30 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@

### Changed

- `all-is-cubes` library:
- `block::BlockAttributes` is now a non-exhaustive struct.
`BlockAttributes` literals should be replaced with `block::Builder::build_attributes()`.

- `all-is-cubes-ui` library:
- `apps::Session::settings()` replaces `graphics_options_mut()`.

Expand Down
12 changes: 6 additions & 6 deletions all-is-cubes-content/src/city.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use itertools::Itertools;
use rand::seq::SliceRandom as _;
use rand::{Rng, SeedableRng as _};

use all_is_cubes::block::{self, text::Font, BlockAttributes, Resolution::*, AIR};
use all_is_cubes::arcstr::literal;
use all_is_cubes::block::{self, text::Font, Resolution::*, AIR};
use all_is_cubes::character::Spawn;
use all_is_cubes::color_block;
use all_is_cubes::content::palette;
Expand Down Expand Up @@ -569,11 +570,10 @@ fn place_one_exhibit<I: Instant>(
bounds_for_info_voxels,
universe.insert_anonymous(exhibit_info_space),
info_resolution,
[BlockAttributes {
display_name: "Exhibit Name".into(),
..Default::default()
}
.into()],
[block::Block::builder()
.display_name(literal!("Exhibit Name"))
.build_attributes()
.into()],
))),
],
});
Expand Down
9 changes: 5 additions & 4 deletions all-is-cubes-content/src/city/exhibits/knot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,11 @@ fn KNOT(ctx: Context<'_>) {
resolution,
txn.insert_anonymous(drawing_space),
&mut |block| {
block.with_modifier(BlockAttributes {
display_name: ctx.exhibit.name.into(),
..BlockAttributes::default()
})
block.with_modifier(
Block::builder()
.display_name(ctx.exhibit.name)
.build_attributes(),
)
},
)?;
Ok((space, txn))
Expand Down
3 changes: 1 addition & 2 deletions all-is-cubes-content/src/city/exhibits/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ pub(super) use rand::SeedableRng as _;

pub(super) use all_is_cubes::arcstr::{self, literal};
pub(super) use all_is_cubes::block::{
self, space_to_blocks, text, Block, BlockAttributes, BlockCollision, Composite,
CompositeOperator, Move,
self, space_to_blocks, text, Block, BlockCollision, Composite, CompositeOperator, Move,
Resolution::{self, *},
RotationPlacementRule, Zoom, AIR,
};
Expand Down
3 changes: 1 addition & 2 deletions all-is-cubes/src/block/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,7 @@ macro_rules! attribute_builder_method {
/// `BlockAttributes::default()` will produce a reasonable set of defaults for “ordinary”
/// blocks.
#[derive(Clone, Eq, Hash, PartialEq)]
#[expect(clippy::exhaustive_structs)]
// TODO: Make this non_exhaustive but give users a way to construct it easily, possibly via block::Builder.
#[non_exhaustive]
#[macro_rules_attribute::derive(derive_attribute_helpers!)]
pub struct BlockAttributes {
/// The name that should be displayed to players.
Expand Down
23 changes: 22 additions & 1 deletion all-is-cubes/src/block/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ use crate::universe::{Handle, Name, Universe, UniverseTransaction};

/// Tool for constructing [`Block`] values conveniently.
///
/// It can also be used to construct [`BlockAttributes`] values.
///
/// To create one, call [`Block::builder()`].
/// ([`Builder::default()`] is also available.)
///
/// # Example
///
/// ```
/// use all_is_cubes::block::{Block, EvaluatedBlock};
/// use all_is_cubes::math::Rgba;
Expand All @@ -40,9 +44,11 @@ use crate::universe::{Handle, Name, Universe, UniverseTransaction};
#[derive(Clone, Debug, Eq, PartialEq)]
#[must_use]
pub struct Builder<P, Txn> {
// public so that `BlockAttributes`'s macros can define methods for us
/// public so that `BlockAttributes`'s macros can define methods for us
pub(in crate::block) attributes: BlockAttributes,

primitive_builder: P,

modifiers: Vec<Modifier>,

/// If this is a [`UniverseTransaction`], then it must be produced for the caller to execute.
Expand All @@ -66,6 +72,21 @@ impl Builder<NeedsPrimitive, ()> {
transaction: (),
}
}

/// Returns a [`BlockAttributes`] instead of building a block with those attributes.
///
/// Panics if any modifiers were added to the builder.
#[track_caller]
pub fn build_attributes(self) -> BlockAttributes {
let Self {
attributes,
primitive_builder: NeedsPrimitive,
modifiers,
transaction: (),
} = self;
assert_eq!(modifiers, []);
attributes
}
}

impl<P, Txn> Builder<P, Txn> {
Expand Down
32 changes: 17 additions & 15 deletions all-is-cubes/tests/alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,30 @@ use all_is_cubes::universe::Universe;

#[test]
fn clone_block_attributes() {
let original = BlockAttributes {
// TODO: Ideally this would be a struct literal with every field,
// but we define BlockAttributes as #[non_exhaustive]. Find a way to prove this is complete.
let original: BlockAttributes = block::Block::builder()
//
// These fields are refcounted or `Copy` and will not allocate when cloned
display_name: arcstr::literal!("hello"),
selectable: true,
animation_hint: block::AnimationHint::UNCHANGING,

placement_action: Some(block::PlacementAction {
.display_name(arcstr::literal!("hello"))
.selectable(true)
.animation_hint(block::AnimationHint::UNCHANGING)
.placement_action(block::PlacementAction {
operation: Operation::Become(block::AIR),
in_front: false,
}),
tick_action: Some(block::TickAction::from(Operation::Become(block::AIR))),
activation_action: Some(Operation::Become(block::AIR)),

})
.tick_action(block::TickAction::from(Operation::Become(block::AIR)))
.activation_action(Operation::Become(block::AIR))
//
// TODO(inventory): This field will allocate when cloned if it is nonempty,
// and we should fix that and test it.
inventory: inv::InvInBlock::default(),

.inventory_config(inv::InvInBlock::default())
//
// These fields currently will never allocate when cloned
rotation_rule: block::RotationPlacementRule::Never,
};
let mut clone = None;
.rotation_rule(block::RotationPlacementRule::Never)
.build_attributes();

let mut clone = None;
assert_no_alloc(|| {
clone = Some(original.clone());
});
Expand Down

0 comments on commit 2f06158

Please sign in to comment.