From 966fb8b06a48125540a63bfb508ce6573d756ee4 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Sun, 14 Jul 2024 19:09:18 -0700 Subject: [PATCH] Add inventory configuration block attribute. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `inv::InvInBlock` is now public, but doesn't have accessors yet so it still can’t actually be properly customized. --- CHANGELOG.md | 13 ++ all-is-cubes-content/src/city/exhibits.rs | 8 + all-is-cubes/src/block.rs | 23 ++- all-is-cubes/src/block/attributes.rs | 22 ++- all-is-cubes/src/block/builder.rs | 12 +- all-is-cubes/src/block/eval/evaluated.rs | 5 + all-is-cubes/src/block/modifier/composite.rs | 6 +- all-is-cubes/src/inv.rs | 2 +- all-is-cubes/src/inv/inv_in_block.rs | 167 ++++++++++++++++--- all-is-cubes/src/save/conversion.rs | 75 ++++++++- all-is-cubes/src/save/schema.rs | 26 +++ all-is-cubes/src/save/tests.rs | 31 +++- all-is-cubes/tests/alloc.rs | 5 + 13 files changed, 352 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9954ae01a..3cf62eeef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## Unrelased + +### Added + +- `all-is-cubes` library: + - Block inventories are now more functional. + - `block::Block::with_inventory` attaches inventory to a block. + - `inv::InvInBlock`, stored in `block::BlockAtributes::inventory`, describes the size and rendering such inventories should have. + +### Changed + +### Removed + ## 0.8.0 (2024-07-08) ### Added diff --git a/all-is-cubes-content/src/city/exhibits.rs b/all-is-cubes-content/src/city/exhibits.rs index 823732a39..847dad27b 100644 --- a/all-is-cubes-content/src/city/exhibits.rs +++ b/all-is-cubes-content/src/city/exhibits.rs @@ -809,9 +809,17 @@ fn INVENTORY(ctx: Context<'_>) { &AIR } })? + .inventory_config(inv::InvInBlock::new_placeholder()) .build_txn(&mut txn); + // TODO: awkward that we have to evaluate + let config = inventory_display_block + .evaluate() + .unwrap() + .attributes + .inventory; let has_items_block = inventory_display_block.with_inventory( + &config, [ inv::Tool::Block(demo_blocks[DemoBlocks::ExhibitBackground].clone()).into(), inv::Tool::Block(color_block!(Rgb::UNIFORM_LUMINANCE_RED)).into(), diff --git a/all-is-cubes/src/block.rs b/all-is-cubes/src/block.rs index 53848c601..46e61bf03 100644 --- a/all-is-cubes/src/block.rs +++ b/all-is-cubes/src/block.rs @@ -11,7 +11,7 @@ use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt; -use crate::inv::{self, InvInBlock}; +use crate::inv; use crate::listen::{Listen as _, Listener}; use crate::math::{GridAab, GridCoordinate, GridPoint, GridRotation, GridVector, Rgb, Rgba, Vol}; use crate::space::{SetCubeError, Space, SpaceChange}; @@ -354,18 +354,25 @@ impl Block { /// Given a block that does not yet have an [`Modifier::Inventory`], add it. /// - /// The size of the added inventory is the maximum of the size set by [`InvInBlock`] + /// The size of the added inventory is the maximum of the size set by [`config`] + /// (which should normally be from the evaluated [`BlockAttributes`] of this block) /// and the size of `contents`. - /// - /// TODO: It is not yet decided what happens when this is called on a block which already - /// has an inventory. Currently, it just attaches another modifier. + //--- + // TODO(inventory): Decide what happens when this is called on a block which + // already has an inventory. Currently, it just attaches another modifier. + // + // TODO(inventory): Decide what happens when `config.size == 0`. + // Should we refrain from adding the modifier? #[must_use] - pub fn with_inventory(self, contents: impl Iterator) -> Block { + pub fn with_inventory( + self, + config: &inv::InvInBlock, + contents: impl Iterator, + ) -> Block { let inventory = inv::Inventory::from_slots( itertools::Itertools::zip_longest( contents, - // TODO(inventory): InvInBlock should be part of block attributes - core::iter::repeat(inv::Slot::Empty).take(InvInBlock::default().size), + core::iter::repeat(inv::Slot::Empty).take(config.size), ) .map(|z| z.into_left()) .collect::>(), diff --git a/all-is-cubes/src/block/attributes.rs b/all-is-cubes/src/block/attributes.rs index ec18b61df..dd3d2dbfc 100644 --- a/all-is-cubes/src/block/attributes.rs +++ b/all-is-cubes/src/block/attributes.rs @@ -5,12 +5,13 @@ use core::{fmt, ops}; use arcstr::ArcStr; +use crate::inv::InvInBlock; use crate::math::{Face6, GridRotation}; use crate::op::Operation; #[cfg(doc)] use crate::{ - block::{Block, BlockDef, Primitive}, + block::{Block, BlockDef, Modifier, Primitive}, space::Space, time::TickSchedule, }; @@ -36,6 +37,10 @@ pub struct BlockAttributes { /// The default value is `true`. pub selectable: bool, + /// Definition of, if this block has an attached [`Modifier::Inventory`], + /// what size and rendering it has. + pub inventory: InvInBlock, + /// Rule about how this block should be rotated, or not, when placed in a [`Space`] by /// some agent not otherwise specifying rotation. /// @@ -70,6 +75,7 @@ impl fmt::Debug for BlockAttributes { let Self { display_name, selectable, + inventory, rotation_rule, tick_action, activation_action, @@ -84,6 +90,9 @@ impl fmt::Debug for BlockAttributes { if *selectable != Self::DEFAULT_REF.selectable { s.field("selectable", selectable); } + if *inventory != Self::DEFAULT_REF.inventory { + s.field("inventory", inventory); + } if *rotation_rule != Self::DEFAULT_REF.rotation_rule { s.field("rotation_rule", rotation_rule); } @@ -105,6 +114,7 @@ impl BlockAttributes { const DEFAULT: Self = BlockAttributes { display_name: arcstr::literal!(""), selectable: true, + inventory: InvInBlock::EMPTY, rotation_rule: RotationPlacementRule::Never, tick_action: None, activation_action: None, @@ -127,13 +137,15 @@ impl BlockAttributes { let Self { display_name: _, selectable: _, + inventory, rotation_rule, tick_action, activation_action, animation_hint: _, } = self; - rotation_rule.rotationally_symmetric() + inventory.rotationally_symmetric() + && rotation_rule.rotationally_symmetric() && !tick_action .as_ref() .is_some_and(|a| !a.rotationally_symmetric()) @@ -146,6 +158,7 @@ impl BlockAttributes { let Self { display_name, selectable, + inventory, rotation_rule, mut tick_action, mut activation_action, @@ -162,6 +175,7 @@ impl BlockAttributes { Self { display_name, selectable, + inventory: inventory.rotate(rotation), rotation_rule: rotation_rule.rotate(rotation), tick_action, activation_action, @@ -186,6 +200,7 @@ impl<'a> arbitrary::Arbitrary<'a> for BlockAttributes { Ok(BlockAttributes { display_name: u.arbitrary::()?.into(), selectable: u.arbitrary()?, + inventory: u.arbitrary()?, rotation_rule: u.arbitrary()?, tick_action: u.arbitrary()?, activation_action: u.arbitrary()?, @@ -197,6 +212,7 @@ impl<'a> arbitrary::Arbitrary<'a> for BlockAttributes { arbitrary::size_hint::and_all(&[ alloc::string::String::size_hint(depth), bool::size_hint(depth), + InvInBlock::size_hint(depth), RotationPlacementRule::size_hint(depth), TickAction::size_hint(depth), AnimationHint::size_hint(depth), @@ -209,11 +225,13 @@ impl crate::universe::VisitHandles for BlockAttributes { let Self { display_name: _, selectable: _, + inventory, rotation_rule: _, tick_action, activation_action, animation_hint: _, } = self; + inventory.visit_handles(visitor); tick_action.visit_handles(visitor); activation_action.visit_handles(visitor); } diff --git a/all-is-cubes/src/block/builder.rs b/all-is-cubes/src/block/builder.rs index e56568c41..174427655 100644 --- a/all-is-cubes/src/block/builder.rs +++ b/all-is-cubes/src/block/builder.rs @@ -10,6 +10,7 @@ use crate::block::{ AnimationHint, Atom, Block, BlockAttributes, BlockCollision, BlockParts, BlockPtr, Modifier, Primitive, Resolution, RotationPlacementRule, AIR, }; +use crate::inv; use crate::math::{Cube, GridAab, GridPoint, Rgb, Rgba}; use crate::space::{SetCubeError, Space}; use crate::transaction::{self, Merge, Transaction}; @@ -91,6 +92,12 @@ impl BlockBuilder { self } + /// Sets the value for [`BlockAttributes::inventory`]. + pub fn inventory_config(mut self, value: inv::InvInBlock) -> Self { + self.attributes.inventory = value; + self + } + /// Sets the value for [`BlockAttributes::rotation_rule`]. pub const fn rotation_rule(mut self, value: RotationPlacementRule) -> Self { self.attributes.rotation_rule = value; @@ -446,6 +453,7 @@ mod tests { fn every_field_nondefault() { let color = Rgba::new(0.1, 0.2, 0.3, 0.4); let emission = Rgb::new(0.1, 3.0, 0.1); + let inventory = inv::InvInBlock::new_placeholder(); let rotation_rule = RotationPlacementRule::Attach { by: Face6::NZ }; let tick_action = Some(TickAction::from(Operation::Become(AIR))); let activation_action = Some(Operation::Become(color_block!(1.0, 1.0, 1.0))); @@ -453,6 +461,7 @@ mod tests { Block::builder() .color(color) .display_name("hello world") + .inventory_config(inventory.clone()) .collision(BlockCollision::None) .rotation_rule(rotation_rule) .selectable(false) @@ -464,8 +473,9 @@ mod tests { Block::from(Atom { attributes: BlockAttributes { display_name: "hello world".into(), - rotation_rule, selectable: false, + inventory, + rotation_rule, tick_action, activation_action, animation_hint: AnimationHint::replacement(block::AnimationChange::Shape), diff --git a/all-is-cubes/src/block/eval/evaluated.rs b/all-is-cubes/src/block/eval/evaluated.rs index 716667a36..605fe1d00 100644 --- a/all-is-cubes/src/block/eval/evaluated.rs +++ b/all-is-cubes/src/block/eval/evaluated.rs @@ -260,6 +260,7 @@ pub(in crate::block) const AIR_EVALUATED_MIN: MinEval = MinEval { const AIR_ATTRIBUTES: BlockAttributes = BlockAttributes { display_name: arcstr::literal!(""), selectable: false, + inventory: crate::inv::InvInBlock::EMPTY, rotation_rule: block::RotationPlacementRule::Never, tick_action: None, activation_action: None, @@ -353,6 +354,7 @@ impl PartialEq for EvKey { BlockAttributes { ref display_name, selectable, + ref inventory, rotation_rule, ref tick_action, ref activation_action, @@ -366,6 +368,7 @@ impl PartialEq for EvKey { display_name.as_ptr(), other.attributes.display_name.as_ptr(), ) && selectable == other.attributes.selectable + && *inventory == other.attributes.inventory && rotation_rule == other.attributes.rotation_rule && *tick_action == other.attributes.tick_action && *activation_action == other.attributes.activation_action @@ -381,6 +384,7 @@ impl core::hash::Hash for EvKey { BlockAttributes { ref display_name, selectable, + ref inventory, rotation_rule, ref tick_action, ref activation_action, @@ -391,6 +395,7 @@ impl core::hash::Hash for EvKey { display_name.as_ptr().hash(state); selectable.hash(state); + inventory.hash(state); rotation_rule.hash(state); tick_action.hash(state); activation_action.hash(state); diff --git a/all-is-cubes/src/block/modifier/composite.rs b/all-is-cubes/src/block/modifier/composite.rs index 0ba05e94a..d6f894611 100644 --- a/all-is-cubes/src/block/modifier/composite.rs +++ b/all-is-cubes/src/block/modifier/composite.rs @@ -198,6 +198,7 @@ fn evaluate_composition( let attributes = block::BlockAttributes { display_name: dst_att.display_name, // TODO merge selectable: src_att.selectable | dst_att.selectable, + inventory: src_att.inventory.concatenate(dst_att.inventory), rotation_rule: dst_att.rotation_rule, // TODO merge tick_action: dst_att.tick_action, // TODO: merge activation_action: operator.blend_operations( @@ -448,8 +449,9 @@ pub(in crate::block) fn render_inventory( return Ok(input); } - // TODO(inventory): InvInBlock should be part of block attributes - let config = crate::inv::InvInBlock::default(); + // TODO(inventory): clone necessary to avoid a borrow conflict + let config = input.attributes.inventory.clone(); + for (slot_index, icon_position) in config.icon_positions() { let Some(placed_icon_bounds) = GridAab::from_lower_size( icon_position, diff --git a/all-is-cubes/src/inv.rs b/all-is-cubes/src/inv.rs index 6f89796da..b9b9995e0 100644 --- a/all-is-cubes/src/inv.rs +++ b/all-is-cubes/src/inv.rs @@ -8,7 +8,7 @@ pub use icons::*; mod inventory; pub use inventory::*; mod inv_in_block; -pub use inv_in_block::InvInBlock; +pub use inv_in_block::*; mod tool; pub use tool::*; diff --git a/all-is-cubes/src/inv/inv_in_block.rs b/all-is-cubes/src/inv/inv_in_block.rs index 792f2729d..7826a3da9 100644 --- a/all-is-cubes/src/inv/inv_in_block.rs +++ b/all-is-cubes/src/inv/inv_in_block.rs @@ -3,15 +3,17 @@ use alloc::vec::Vec; use crate::block::Resolution; -use crate::math::{GridCoordinate, GridPoint, GridVector}; +use crate::math::{GridCoordinate, GridPoint, GridRotation, GridVector, Gridgid}; #[cfg(doc)] use crate::block::Modifier; /// Defines how a [`Modifier::Inventory`] should be configured and displayed within a block. -/// -/// TODO: Needs a better name. -#[derive(Clone, Debug, Eq, PartialEq)] +//--- +// TODO(inventory): better name? +// TODO(inventory): needs accessors or public fields +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct InvInBlock { /// Number of slots the inventory should have. pub(crate) size: usize, // TODO: use platform-independent max size @@ -27,10 +29,15 @@ pub struct InvInBlock { /// beyond this resolution. pub(crate) icon_resolution: Resolution, + // TODO: following the rule for all `BlockAttributes` being cheap to clone, + // this should be `Arc<[IconRow]>`, but that's mildly inconvenient for `Arbitrary`, so + // I'm not bothering for this first iteration. + // (But maybe we should be handling that at a higher level of the structure.) pub(crate) icon_rows: Vec, } -#[derive(Clone, Debug, Eq, PartialEq)] +/// Positioning of a displayed row of inventory icons; part of [`InvInBlock`]. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct IconRow { pub(crate) first_slot: usize, pub(crate) count: usize, @@ -39,25 +46,17 @@ pub struct IconRow { } impl InvInBlock { - /// Returns which inventory slots should be rendered as icons, and the lower corners - /// of the icons. - pub(crate) fn icon_positions(&self) -> impl Iterator + '_ { - self.icon_rows.iter().flat_map(|row| { - (0..row.count).map_while(move |sub_index| { - let slot_index = row.first_slot.checked_add(sub_index)?; - Some(( - slot_index, - // TODO: this should be checked arithmetic - row.origin + row.stride * GridCoordinate::try_from(sub_index).ok()?, - )) - }) - }) - } -} + /// Value for blocks which should not carry inventories. + pub(crate) const EMPTY: Self = Self { + size: 0, + icon_scale: Resolution::R1, // arbitrary + icon_resolution: Resolution::R1, // arbitrary + icon_rows: Vec::new(), + }; -impl Default for InvInBlock { - fn default() -> Self { - // TODO: placeholder; the real default should be empty (zero slots, invisible). + // TODO(inventory): this substitutes for the lack of ability to actually construct one publicly + #[doc(hidden)] + pub fn new_placeholder() -> Self { Self { size: 1, icon_scale: Resolution::R4, @@ -84,6 +83,122 @@ impl Default for InvInBlock { ], } } + + /// Returns which inventory slots should be rendered as icons, and the lower corners + /// of the icons. + pub(crate) fn icon_positions(&self) -> impl Iterator + '_ { + self.icon_rows.iter().flat_map(|row| { + (0..row.count).map_while(move |sub_index| { + let slot_index = row.first_slot.checked_add(sub_index)?; + Some(( + slot_index, + // TODO: this should be checked arithmetic + row.origin + row.stride * GridCoordinate::try_from(sub_index).ok()?, + )) + }) + }) + } + + pub(crate) fn rotationally_symmetric(&self) -> bool { + // If it doesn't display any icons, then it's symmetric. + self.icon_rows.is_empty() + } + + pub(crate) fn rotate(self, rotation: GridRotation) -> Self { + let Self { + size, + icon_scale, + icon_resolution, + icon_rows, + } = self; + let transform = rotation.to_positive_octant_transform(icon_resolution.into()); + Self { + size, + icon_scale, + icon_resolution, + icon_rows: icon_rows + .into_iter() + .map(|row| row.rotate(transform)) + .collect(), + } + } + + /// Combine the two inputs to form one which has the size and display of both. + pub(crate) fn concatenate(self, other: InvInBlock) -> InvInBlock { + if self.size == 0 { + other + } else { + Self { + size: self.size.saturating_add(other.size), + // TODO(inventory): scale and resolution need adaptation + icon_scale: self.icon_scale, + icon_resolution: self.icon_resolution, + icon_rows: self + .icon_rows + .into_iter() + .chain(other.icon_rows.into_iter().filter_map(|mut row| { + row.first_slot = row.first_slot.checked_add(self.size)?; + Some(row) + })) + .collect(), + } + } + } +} + +impl Default for InvInBlock { + /// Returns [`InvInBlock::EMPTY`]. + fn default() -> Self { + Self::EMPTY + } +} + +impl crate::universe::VisitHandles for InvInBlock { + fn visit_handles(&self, _: &mut dyn crate::universe::HandleVisitor) { + let Self { + size: _, + icon_scale: _, + icon_resolution: _, + icon_rows: _, + } = self; + } +} + +impl IconRow { + fn rotate(self, transform: Gridgid) -> Self { + // TODO: The icons themselves (not their positions) need to be rotated, + // but this is not supported yet. + Self { + first_slot: self.first_slot, + count: self.count, + origin: transform.transform_point(self.origin), // TODO: does not account for size of icon + stride: transform.rotation.transform_vector(self.stride), + } + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for IconRow { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + // TODO: there are lots of extremely useless out-of-bounds values here; + // bias to useful ones? + Ok(Self { + first_slot: u.arbitrary()?, + count: u.arbitrary()?, + origin: <[i32; 3]>::arbitrary(u)?.into(), + stride: <[i32; 3]>::arbitrary(u)?.into(), + }) + } + + fn size_hint(depth: usize) -> (usize, Option) { + use arbitrary::{size_hint::and_all, Arbitrary}; + and_all(&[ + ::size_hint(depth), + ::size_hint(depth), + <[GridCoordinate; 3] as Arbitrary>::size_hint(depth), + <[GridCoordinate; 3] as Arbitrary>::size_hint(depth), + ]) + } } #[cfg(test)] @@ -92,10 +207,10 @@ mod tests { use euclid::point3; use pretty_assertions::assert_eq; - // TODO: this test should be revised to create an `InvInBlock` for testing instead of the default. #[test] - fn default_icon_positions() { - let iib = InvInBlock::default(); + fn icon_positions_output() { + // TODO: this test should be revised to create a specific `InvInBlock` value for testingg + let iib = InvInBlock::new_placeholder(); assert_eq!( iib.icon_positions().take(10).collect::>(), vec![ diff --git a/all-is-cubes/src/save/conversion.rs b/all-is-cubes/src/save/conversion.rs index 818d4afec..5944132bc 100644 --- a/all-is-cubes/src/save/conversion.rs +++ b/all-is-cubes/src/save/conversion.rs @@ -188,6 +188,7 @@ mod block { let &BlockAttributes { ref display_name, selectable, + ref inventory, rotation_rule, ref tick_action, ref activation_action, @@ -196,6 +197,7 @@ mod block { schema::BlockAttributesV1Ser { display_name: display_name.clone(), selectable, + inventory: inventory.into(), rotation_rule: rotation_rule.into(), tick_action: tick_action.as_ref().map( |&TickAction { @@ -217,6 +219,7 @@ mod block { let schema::BlockAttributesV1Ser { display_name, selectable, + inventory, rotation_rule, tick_action, activation_action, @@ -225,6 +228,7 @@ mod block { Self { display_name, selectable, + inventory: inventory.into(), rotation_rule: rotation_rule.into(), tick_action: tick_action.map(|schema::TickActionSer { operation, period }| { TickAction { @@ -506,7 +510,7 @@ mod block { mod inv { use super::*; - use crate::inv::{Inventory, Slot, Tool}; + use crate::inv::{self, Inventory, Slot, Tool}; impl Serialize for Inventory { fn serialize(&self, serializer: S) -> Result @@ -599,6 +603,75 @@ mod inv { }) } } + + impl From<&inv::InvInBlock> for schema::InvInBlockSer { + fn from(value: &inv::InvInBlock) -> Self { + let inv::InvInBlock { + size, + icon_scale, + icon_resolution, + ref icon_rows, + } = *value; + schema::InvInBlockSer::InvInBlockV1 { + size, + icon_scale, + icon_resolution, + icon_rows: icon_rows.iter().map(schema::IconRowSerV1::from).collect(), + } + } + } + + impl From for inv::InvInBlock { + fn from(value: schema::InvInBlockSer) -> Self { + match value { + schema::InvInBlockSer::InvInBlockV1 { + size, + icon_scale, + icon_resolution, + icon_rows, + } => inv::InvInBlock { + size, + icon_scale, + icon_resolution, + icon_rows: icon_rows.into_iter().map(inv::IconRow::from).collect(), + }, + } + } + } + + impl From<&inv::IconRow> for schema::IconRowSerV1 { + fn from(value: &inv::IconRow) -> Self { + let inv::IconRow { + first_slot, + count, + origin, + stride, + } = *value; + schema::IconRowSerV1 { + first_slot, + count, + origin: origin.into(), + stride: stride.into(), + } + } + } + + impl From for inv::IconRow { + fn from(value: schema::IconRowSerV1) -> Self { + let schema::IconRowSerV1 { + first_slot, + count, + origin, + stride, + } = value; + inv::IconRow { + first_slot, + count, + origin: origin.into(), + stride: stride.into(), + } + } + } } mod op { diff --git a/all-is-cubes/src/save/schema.rs b/all-is-cubes/src/save/schema.rs index aa75d64d0..7fc2452db 100644 --- a/all-is-cubes/src/save/schema.rs +++ b/all-is-cubes/src/save/schema.rs @@ -107,6 +107,8 @@ pub(crate) struct BlockAttributesV1Ser<'a> { #[serde(default = "return_true", skip_serializing_if = "is_true")] pub selectable: bool, #[serde(default, skip_serializing_if = "is_default")] + pub inventory: InvInBlockSer, + #[serde(default, skip_serializing_if = "is_default")] pub rotation_rule: RotationPlacementRuleSer, #[serde(default, skip_serializing_if = "Option::is_none")] pub tick_action: Option>, @@ -322,6 +324,30 @@ pub(crate) enum ToolSer { CustomV1 { op: op::Operation, icon: Block }, } +#[derive(Debug, Deserialize, Serialize, PartialEq)] +#[serde(tag = "type")] +pub(crate) enum InvInBlockSer { + InvInBlockV1 { + size: usize, // TODO: use platform-independent max size + icon_scale: block::Resolution, + icon_resolution: block::Resolution, + icon_rows: Vec, + }, +} +impl Default for InvInBlockSer { + fn default() -> Self { + (&inv::InvInBlock::EMPTY).into() + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub(crate) struct IconRowSerV1 { + pub(crate) first_slot: usize, + pub(crate) count: usize, + pub(crate) origin: [i32; 3], + pub(crate) stride: [i32; 3], +} + //------------------------------------------------------------------------------------------------// // Schema corresponding to the `math` module diff --git a/all-is-cubes/src/save/tests.rs b/all-is-cubes/src/save/tests.rs index ad3766e3f..c0c8166df 100644 --- a/all-is-cubes/src/save/tests.rs +++ b/all-is-cubes/src/save/tests.rs @@ -16,7 +16,7 @@ use crate::block::{ }; use crate::character::{Character, Spawn}; use crate::content::make_some_blocks; -use crate::inv::{EphemeralOpaque, Inventory, Tool}; +use crate::inv::{self, EphemeralOpaque, Inventory, Tool}; use crate::math::{notnan, Cube, Face6, GridAab, GridRotation, Rgb, Rgba}; use crate::save::compress::{GzSerde, Leu16}; use crate::space::{self, BlockIndex, LightPhysics, Space, SpacePhysics}; @@ -172,6 +172,7 @@ fn block_atom_with_all_attributes() { .collision(block::BlockCollision::None) .display_name("foo") .selectable(false) + .inventory_config(inv::InvInBlock::new_placeholder()) .rotation_rule(block::RotationPlacementRule::Attach { by: Face6::PX }) .tick_action(Some(block::TickAction { operation: op::Operation::Become(AIR), @@ -191,7 +192,33 @@ fn block_atom_with_all_attributes() { "collision": "NoneV1", "display_name": "foo", "selectable": false, - "rotation_rule": { + "inventory": { + "type": "InvInBlockV1", + "size": 1, + "icon_scale": 4, + "icon_resolution": 16, + "icon_rows": [ + { + "count": 3, + "first_slot": 0, + "origin": [1, 1, 1], + "stride": [5, 0, 0], + }, + { + "count": 3, + "first_slot": 3, + "origin": [1, 1, 6], + "stride": [5, 0, 0], + }, + { + "count": 3, + "first_slot": 6, + "origin": [1, 1, 11], + "stride": [5, 0, 0], + }, + ], + }, + "rotation_rule": { "type": "AttachV1", "by": "PX", }, diff --git a/all-is-cubes/tests/alloc.rs b/all-is-cubes/tests/alloc.rs index 7fbffdbd8..67c86462b 100644 --- a/all-is-cubes/tests/alloc.rs +++ b/all-is-cubes/tests/alloc.rs @@ -2,6 +2,7 @@ //! //! In a separate test crate to avoid modifying the global allocator elsewhere. +use all_is_cubes::inv; use all_is_cubes::op::Operation; use allocation_counter::{measure, AllocationInfo}; @@ -21,6 +22,10 @@ fn clone_block_attributes() { tick_action: Some(block::TickAction::from(Operation::Become(block::AIR))), activation_action: Some(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(), + // These fields currently will never allocate when cloned rotation_rule: block::RotationPlacementRule::Never, };