mindustry logic execution, map- and schematic- parsing and rendering
cooler macros
bendn 2023-06-23
parent b18937f · commit 4678316
-rw-r--r--Cargo.toml3
-rw-r--r--src/access.rs154
-rw-r--r--src/block/base.rs208
-rw-r--r--src/block/content.rs833
-rw-r--r--src/block/defense.rs187
-rw-r--r--src/block/extraction.rs14
-rw-r--r--src/block/factory.rs40
-rw-r--r--src/block/fluid.rs277
-rw-r--r--src/block/logic.rs1129
-rw-r--r--src/block/mod.rs581
-rw-r--r--src/block/payload.rs561
-rw-r--r--src/block/power.rs270
-rw-r--r--src/block/simple.rs137
-rw-r--r--src/block/transport.rs573
-rw-r--r--src/block/turret.rs36
-rw-r--r--src/content.rs211
-rw-r--r--src/data/base64.rs578
-rw-r--r--src/data/command.rs11
-rw-r--r--src/data/dynamic.rs1019
-rw-r--r--src/data/mod.rs588
-rw-r--r--src/data/schematic.rs2571
-rw-r--r--src/exe/args.rs862
-rw-r--r--src/exe/edit.rs2680
-rw-r--r--src/exe/mod.rs24
-rw-r--r--src/exe/print.rs333
-rw-r--r--src/fluid/mod.rs31
-rw-r--r--src/item/mod.rs110
-rw-r--r--src/item/storage.rs729
-rw-r--r--src/logic/mod.rs85
-rw-r--r--src/modifier.rs51
-rw-r--r--src/registry.rs64
-rw-r--r--src/team.rs183
-rw-r--r--src/unit/mod.rs139
-rw-r--r--src/utils/once_cell.rs292
34 files changed, 7671 insertions, 7893 deletions
diff --git a/Cargo.toml b/Cargo.toml
index fd3391e..efb00b2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,7 +8,8 @@ repository = "https://github.com/KosmosPrime/plandustry.git"
license = "GPL-3.0"
[dependencies]
-flate2 = {version = "1.0", features = ["zlib"], default-features = false}
+flate2 = { version = "1.0", features = ["zlib"], default-features = false }
+paste = "1.0.12"
[[bin]]
name = "plandustry"
diff --git a/src/access.rs b/src/access.rs
index 96369c8..8498741 100644
--- a/src/access.rs
+++ b/src/access.rs
@@ -8,121 +8,97 @@ pub type BoxAccess<'a, D> = Access<'a, Box<D>, D>;
// Similar to Cow but doesn't require ToOwned
#[derive(Clone, Debug)]
-pub enum Access<'a, T: AsRef<B>, B: ?Sized>
-{
+pub enum Access<'a, T: AsRef<B>, B: ?Sized> {
Borrowed(&'a B),
Owned(T),
}
-impl<'a, T: AsRef<B>, B> Access<'a, T, B>
-{
- pub const fn is_borrowed(&self) -> bool
- {
- match self
- {
- Self::Borrowed(..) => true,
- _ => false,
- }
- }
-
- pub const fn is_owned(&self) -> bool
- {
- match self
- {
- Self::Owned(..) => true,
- _ => false,
- }
- }
+impl<'a, T: AsRef<B>, B> Access<'a, T, B> {
+ pub const fn is_borrowed(&self) -> bool {
+ match self {
+ Self::Borrowed(..) => true,
+ _ => false,
+ }
+ }
+
+ pub const fn is_owned(&self) -> bool {
+ match self {
+ Self::Owned(..) => true,
+ _ => false,
+ }
+ }
}
-impl<'a, T: AsRef<B>, B: ?Sized> From<T> for Access<'a, T, B>
-{
- fn from(value: T) -> Self
- {
- Self::Owned(value)
- }
+impl<'a, T: AsRef<B>, B: ?Sized> From<T> for Access<'a, T, B> {
+ fn from(value: T) -> Self {
+ Self::Owned(value)
+ }
}
-impl<'a, T: AsRef<B>, B: ?Sized> AsRef<B> for Access<'a, T, B>
-{
- fn as_ref(&self) -> &B
- {
- self
- }
+impl<'a, T: AsRef<B>, B: ?Sized> AsRef<B> for Access<'a, T, B> {
+ fn as_ref(&self) -> &B {
+ self
+ }
}
-impl<'a, T: AsRef<B>, B: ?Sized> Borrow<B> for Access<'a, T, B>
-{
- fn borrow(&self) -> &B
- {
- match self
- {
- Self::Borrowed(r) => *r,
- Self::Owned(v) => v.as_ref(),
- }
- }
+impl<'a, T: AsRef<B>, B: ?Sized> Borrow<B> for Access<'a, T, B> {
+ fn borrow(&self) -> &B {
+ match self {
+ Self::Borrowed(r) => *r,
+ Self::Owned(v) => v.as_ref(),
+ }
+ }
}
-impl<'a, T: AsRef<B> + Default, B: ?Sized> Default for Access<'a, T, B>
-{
- fn default() -> Self
- {
- Self::Owned(T::default())
- }
+impl<'a, T: AsRef<B> + Default, B: ?Sized> Default for Access<'a, T, B> {
+ fn default() -> Self {
+ Self::Owned(T::default())
+ }
}
-impl<'a, T: AsRef<B>, B: ?Sized> Deref for Access<'a, T, B>
-{
- type Target = B;
-
- fn deref(&self) -> &Self::Target
- {
- match self
- {
- Self::Borrowed(r) => *r,
- Self::Owned(v) => v.as_ref(),
- }
- }
+impl<'a, T: AsRef<B>, B: ?Sized> Deref for Access<'a, T, B> {
+ type Target = B;
+
+ fn deref(&self) -> &Self::Target {
+ match self {
+ Self::Borrowed(r) => *r,
+ Self::Owned(v) => v.as_ref(),
+ }
+ }
}
-impl<'a, T: AsRef<B>, B: ?Sized + fmt::Display> fmt::Display for Access<'a, T, B>
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- B::fmt(self, f)
- }
+impl<'a, T: AsRef<B>, B: ?Sized + fmt::Display> fmt::Display for Access<'a, T, B> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ B::fmt(self, f)
+ }
}
impl<'a, T: AsRef<B>, B: ?Sized + Eq> Eq for Access<'a, T, B> {}
-impl<'a, T: AsRef<B>, B: ?Sized + Hash> Hash for Access<'a, T, B>
-{
- fn hash<H: Hasher>(&self, state: &mut H)
- {
- B::hash(self, state)
- }
+impl<'a, T: AsRef<B>, B: ?Sized + Hash> Hash for Access<'a, T, B> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ B::hash(self, state)
+ }
}
-impl<'a, T: AsRef<B>, B: ?Sized + Ord> Ord for Access<'a, T, B>
-{
- fn cmp(&self, other: &Self) -> Ordering
- {
- B::cmp(self, other)
- }
+impl<'a, T: AsRef<B>, B: ?Sized + Ord> Ord for Access<'a, T, B> {
+ fn cmp(&self, other: &Self) -> Ordering {
+ B::cmp(self, other)
+ }
}
-impl<'a, 'b, T: AsRef<B>, B: ?Sized + PartialEq<C>, U: AsRef<C>, C: ?Sized> PartialEq<Access<'b, U, C>> for Access<'a, T, B>
+impl<'a, 'b, T: AsRef<B>, B: ?Sized + PartialEq<C>, U: AsRef<C>, C: ?Sized>
+ PartialEq<Access<'b, U, C>> for Access<'a, T, B>
{
- fn eq(&self, other: &Access<'b, U, C>) -> bool
- {
- B::eq(self, other)
- }
+ fn eq(&self, other: &Access<'b, U, C>) -> bool {
+ B::eq(self, other)
+ }
}
-impl<'a, 'b, T: AsRef<B>, B: ?Sized + PartialOrd<C>, U: AsRef<C>, C: ?Sized> PartialOrd<Access<'b, U, C>> for Access<'a, T, B>
+impl<'a, 'b, T: AsRef<B>, B: ?Sized + PartialOrd<C>, U: AsRef<C>, C: ?Sized>
+ PartialOrd<Access<'b, U, C>> for Access<'a, T, B>
{
- fn partial_cmp(&self, other: &Access<'b, U, C>) -> Option<Ordering>
- {
- B::partial_cmp(self, other)
- }
+ fn partial_cmp(&self, other: &Access<'b, U, C>) -> Option<Ordering> {
+ B::partial_cmp(self, other)
+ }
}
diff --git a/src/block/base.rs b/src/block/base.rs
index e62fd86..da1cdef 100644
--- a/src/block/base.rs
+++ b/src/block/base.rs
@@ -1,127 +1,119 @@
use std::any::Any;
-use crate::block::{BlockLogic, DataConvertError, DeserializeError, make_register, SerializeError};
-use crate::block::simple::{BuildCost, cost, SimpleBlock, state_impl};
+use crate::block::simple::{cost, state_impl, BuildCost, SimpleBlock};
use crate::block::transport::ItemBlock;
-use crate::data::GridPos;
+use crate::block::{make_register, BlockLogic, DataConvertError, DeserializeError, SerializeError};
use crate::data::dynamic::{DynData, DynType};
+use crate::data::GridPos;
use crate::item::storage::Storage;
-make_register!
-(
- MENDER: "mender" => SimpleBlock::new(1, true, cost!(Copper: 25, Lead: 30));
- MEND_PROJECTOR: "mend-projector" => SimpleBlock::new(2, true, cost!(Copper: 50, Lead: 100, Titanium: 25, Silicon: 40));
- OVERDRIVE_PROJECTOR: "overdrive-projector" => SimpleBlock::new(2, true, cost!(Lead: 100, Titanium: 75, Silicon: 75, Plastanium: 30));
- OVERDRIVE_DOME: "overdrive-dome" => SimpleBlock::new(3, true, cost!(Lead: 200, Titanium: 130, Silicon: 130, Plastanium: 80, SurgeAlloy: 120));
- FORCE_PROJECTOR: "force-projector" => SimpleBlock::new(3, true, cost!(Lead: 100, Titanium: 75, Silicon: 125));
- SHOCK_MINE: "shock-mine" => SimpleBlock::new(1, true, cost!(Lead: 25, Silicon: 12));
- CORE_SHARD: "core-shard" => SimpleBlock::new(3, true, cost!(Copper: 1000, Lead: 800));
- CORE_FOUNDATION: "core-foundation" => SimpleBlock::new(4, true, cost!(Copper: 3000, Lead: 3000, Silicon: 2000));
- CORE_NUCLEUS: "core-nucleus" => SimpleBlock::new(5, true, cost!(Copper: 8000, Lead: 8000, Thorium: 4000, Silicon: 5000));
- CONTAINER: "container" => SimpleBlock::new(2, true, cost!(Titanium: 100));
- VAULT: "vault" => SimpleBlock::new(3, true, cost!(Titanium: 250, Thorium: 125));
- UNLOADER: "unloader" => ItemBlock::new(1, true, cost!(Titanium: 25, Silicon: 30));
- ILLUMINATOR: "illuminator" => LampBlock::new(1, true, cost!(Lead: 8, Graphite: 12, Silicon: 8));
- LAUNCH_PAD: "launch-pad" => SimpleBlock::new(3, true, cost!(Copper: 350, Lead: 200, Titanium: 150, Silicon: 140));
-);
+make_register! {
+ "mender" => SimpleBlock::new(1, true, cost!(Copper: 25, Lead: 30));
+ "mend-projector" => SimpleBlock::new(2, true, cost!(Copper: 50, Lead: 100, Titanium: 25, Silicon: 40));
+ "overdrive-projector" => SimpleBlock::new(2, true, cost!(Lead: 100, Titanium: 75, Silicon: 75, Plastanium: 30));
+ "overdrive-dome" => SimpleBlock::new(3, true, cost!(Lead: 200, Titanium: 130, Silicon: 130, Plastanium: 80, SurgeAlloy: 120));
+ "force-projector" => SimpleBlock::new(3, true, cost!(Lead: 100, Titanium: 75, Silicon: 125));
+ "shock-mine" => SimpleBlock::new(1, true, cost!(Lead: 25, Silicon: 12));
+ "core-shard" => SimpleBlock::new(3, true, cost!(Copper: 1000, Lead: 800));
+ "core-foundation" => SimpleBlock::new(4, true, cost!(Copper: 3000, Lead: 3000, Silicon: 2000));
+ "core-nucleus" => SimpleBlock::new(5, true, cost!(Copper: 8000, Lead: 8000, Thorium: 4000, Silicon: 5000));
+ "container" => SimpleBlock::new(2, true, cost!(Titanium: 100));
+ "vault" => SimpleBlock::new(3, true, cost!(Titanium: 250, Thorium: 125));
+ "unloader" => ItemBlock::new(1, true, cost!(Titanium: 25, Silicon: 30));
+ "illuminator" => LampBlock::new(1, true, cost!(Lead: 8, Graphite: 12, Silicon: 8));
+ "launch-pad" => SimpleBlock::new(3, true, cost!(Copper: 350, Lead: 200, Titanium: 150, Silicon: 140));
+}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RGBA(u8, u8, u8, u8);
-impl From<u32> for RGBA
-{
- fn from(value: u32) -> Self
- {
- Self((value >> 24) as u8, (value >> 16) as u8, (value >> 8) as u8, value as u8)
- }
+impl From<u32> for RGBA {
+ fn from(value: u32) -> Self {
+ Self(
+ (value >> 24) as u8,
+ (value >> 16) as u8,
+ (value >> 8) as u8,
+ value as u8,
+ )
+ }
}
-impl From<RGBA> for u32
-{
- fn from(value: RGBA) -> Self
- {
- ((value.0 as u32) << 24) | ((value.1 as u32) << 16) | ((value.2 as u32) << 8) | (value.3 as u32)
- }
+impl From<RGBA> for u32 {
+ fn from(value: RGBA) -> Self {
+ ((value.0 as u32) << 24)
+ | ((value.1 as u32) << 16)
+ | ((value.2 as u32) << 8)
+ | (value.3 as u32)
+ }
}
-pub struct LampBlock
-{
- size: u8,
- symmetric: bool,
- build_cost: BuildCost,
+pub struct LampBlock {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
}
-impl LampBlock
-{
- pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self
- {
- if size == 0
- {
- panic!("invalid size");
- }
- Self{size, symmetric, build_cost}
- }
-
- state_impl!(pub RGBA);
+impl LampBlock {
+ pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self {
+ if size == 0 {
+ panic!("invalid size");
+ }
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ }
+ }
+
+ state_impl!(pub RGBA);
}
-impl BlockLogic for LampBlock
-{
- fn get_size(&self) -> u8
- {
- self.size
- }
-
- fn is_symmetric(&self) -> bool
- {
- self.symmetric
- }
-
- fn create_build_cost(&self) -> Option<Storage>
- {
- if !self.build_cost.is_empty()
- {
- let mut storage = Storage::new();
- for (ty, cnt) in self.build_cost
- {
- storage.add(*ty, *cnt, u32::MAX);
- }
- Some(storage)
- }
- else {None}
- }
-
- fn data_from_i32(&self, config: i32, _: GridPos) -> Result<DynData, DataConvertError>
- {
- Ok(DynData::Int(config))
- }
-
- fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError>
- {
- match data
- {
- DynData::Int(rgba) => Ok(Some(Self::create_state(RGBA::from(rgba as u32)))),
- _ => Err(DeserializeError::InvalidType{have: data.get_type(), expect: DynType::Int}),
- }
- }
-
- fn clone_state(&self, state: &dyn Any) -> Box<dyn Any>
- {
- let state = Self::get_state(state);
- Box::new(Self::create_state(*state))
- }
-
- fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool)
- {
- }
-
- fn rotate_state(&self, _: &mut dyn Any, _: bool)
- {
- }
-
- fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError>
- {
- let state = Self::get_state(state);
- Ok(DynData::Int(u32::from(*state) as i32))
- }
+impl BlockLogic for LampBlock {
+ fn get_size(&self) -> u8 {
+ self.size
+ }
+
+ fn is_symmetric(&self) -> bool {
+ self.symmetric
+ }
+
+ fn create_build_cost(&self) -> Option<Storage> {
+ if !self.build_cost.is_empty() {
+ let mut storage = Storage::new();
+ for (ty, cnt) in self.build_cost {
+ storage.add(*ty, *cnt, u32::MAX);
+ }
+ Some(storage)
+ } else {
+ None
+ }
+ }
+
+ fn data_from_i32(&self, config: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ Ok(DynData::Int(config))
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> {
+ match data {
+ DynData::Int(rgba) => Ok(Some(Self::create_state(RGBA::from(rgba as u32)))),
+ _ => Err(DeserializeError::InvalidType {
+ have: data.get_type(),
+ expect: DynType::Int,
+ }),
+ }
+ }
+
+ fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> {
+ let state = Self::get_state(state);
+ Box::new(Self::create_state(*state))
+ }
+
+ fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool) {}
+
+ fn rotate_state(&self, _: &mut dyn Any, _: bool) {}
+
+ fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> {
+ let state = Self::get_state(state);
+ Ok(DynData::Int(u32::from(*state) as i32))
+ }
}
diff --git a/src/block/content.rs b/src/block/content.rs
index b77c1af..832ebb4 100644
--- a/src/block/content.rs
+++ b/src/block/content.rs
@@ -1,420 +1,419 @@
use crate::content::content_enum;
-content_enum!
-{
- pub enum Type / Block for u16 | TryFromU16Error
- {
- Air => "air",
- Spawn => "spawn",
- Cliff => "cliff",
- Build1 => "build1",
- Build2 => "build2",
- Build3 => "build3",
- Build4 => "build4",
- Build5 => "build5",
- Build6 => "build6",
- Build7 => "build7",
- Build8 => "build8",
- Build9 => "build9",
- Build10 => "build10",
- Build11 => "build11",
- Build12 => "build12",
- Build13 => "build13",
- Build14 => "build14",
- Build15 => "build15",
- Build16 => "build16",
- DeepWater => "deep-water",
- ShallowWater => "shallow-water",
- TaintedWater => "tainted-water",
- DeepTaintedWater => "deep-tainted-water",
- DarksandTaintedWater => "darksand-tainted-water",
- SandWater => "sand-water",
- DarksandWater => "darksand-water",
- Tar => "tar",
- PooledCryofluid => "pooled-cryofluid",
- MoltenSlag => "molten-slag",
- Space => "space",
- Empty => "empty",
- Stone => "stone",
- CraterStone => "crater-stone",
- Char => "char",
- Basalt => "basalt",
- Hotrock => "hotrock",
- Magmarock => "magmarock",
- SandFloor => "sand-floor",
- Darksand => "darksand",
- Dirt => "dirt",
- Mud => "mud",
- Dacite => "dacite",
- Rhyolite => "rhyolite",
- RhyoliteCrater => "rhyolite-crater",
- RoughRhyolite => "rough-rhyolite",
- Regolith => "regolith",
- YellowStone => "yellow-stone",
- CarbonStone => "carbon-stone",
- FerricStone => "ferric-stone",
- FerricCraters => "ferric-craters",
- BeryllicStone => "beryllic-stone",
- CrystallineStone => "crystalline-stone",
- CrystalFloor => "crystal-floor",
- YellowStonePlates => "yellow-stone-plates",
- RedStone => "red-stone",
- DenseRedStone => "dense-red-stone",
- RedIce => "red-ice",
- ArkyciteFloor => "arkycite-floor",
- ArkyicStone => "arkyic-stone",
- RhyoliteVent => "rhyolite-vent",
- CarbonVent => "carbon-vent",
- ArkyicVent => "arkyic-vent",
- YellowStoneVent => "yellow-stone-vent",
- RedStoneVent => "red-stone-vent",
- CrystallineVent => "crystalline-vent",
- Redmat => "redmat",
- Bluemat => "bluemat",
- Grass => "grass",
- Salt => "salt",
- Snow => "snow",
- Ice => "ice",
- IceSnow => "ice-snow",
- Shale => "shale",
- Moss => "moss",
- CoreZone => "core-zone",
- SporeMoss => "spore-moss",
- StoneWall => "stone-wall",
- SporeWall => "spore-wall",
- DirtWall => "dirt-wall",
- DaciteWall => "dacite-wall",
- IceWall => "ice-wall",
- SnowWall => "snow-wall",
- DuneWall => "dune-wall",
- RegolithWall => "regolith-wall",
- YellowStoneWall => "yellow-stone-wall",
- RhyoliteWall => "rhyolite-wall",
- CarbonWall => "carbon-wall",
- FerricStoneWall => "ferric-stone-wall",
- BeryllicStoneWall => "beryllic-stone-wall",
- ArkyicWall => "arkyic-wall",
- CrystallineStoneWall => "crystalline-stone-wall",
- RedIceWall => "red-ice-wall",
- RedStoneWall => "red-stone-wall",
- RedDiamondWall => "red-diamond-wall",
- SandWall => "sand-wall",
- SaltWall => "salt-wall",
- Shrubs => "shrubs",
- ShaleWall => "shale-wall",
- SporePine => "spore-pine",
- SnowPine => "snow-pine",
- Pine => "pine",
- WhiteTreeDead => "white-tree-dead",
- WhiteTree => "white-tree",
- SporeCluster => "spore-cluster",
- Redweed => "redweed",
- PurBush => "pur-bush",
- Yellowcoral => "yellowcoral",
- Boulder => "boulder",
- SnowBoulder => "snow-boulder",
- ShaleBoulder => "shale-boulder",
- SandBoulder => "sand-boulder",
- DaciteBoulder => "dacite-boulder",
- BasaltBoulder => "basalt-boulder",
- CarbonBoulder => "carbon-boulder",
- FerricBoulder => "ferric-boulder",
- BeryllicBoulder => "beryllic-boulder",
- YellowStoneBoulder => "yellow-stone-boulder",
- ArkyicBoulder => "arkyic-boulder",
- CrystalCluster => "crystal-cluster",
- VibrantCrystalCluster => "vibrant-crystal-cluster",
- CrystalBlocks => "crystal-blocks",
- CrystalOrbs => "crystal-orbs",
- CrystallineBoulder => "crystalline-boulder",
- RedIceBoulder => "red-ice-boulder",
- RhyoliteBoulder => "rhyolite-boulder",
- RedStoneBoulder => "red-stone-boulder",
- MetalFloor => "metal-floor",
- MetalFloorDamaged => "metal-floor-damaged",
- MetalFloor2 => "metal-floor-2",
- MetalFloor3 => "metal-floor-3",
- MetalFloor4 => "metal-floor-4",
- MetalFloor5 => "metal-floor-5",
- DarkPanel1 => "dark-panel-1",
- DarkPanel2 => "dark-panel-2",
- DarkPanel3 => "dark-panel-3",
- DarkPanel4 => "dark-panel-4",
- DarkPanel5 => "dark-panel-5",
- DarkPanel6 => "dark-panel-6",
- DarkMetal => "dark-metal",
- Pebbles => "pebbles",
- Tendrils => "tendrils",
- OreCopper => "ore-copper",
- OreLead => "ore-lead",
- OreScrap => "ore-scrap",
- OreCoal => "ore-coal",
- OreTitanium => "ore-titanium",
- OreThorium => "ore-thorium",
- OreBeryllium => "ore-beryllium",
- OreTungsten => "ore-tungsten",
- OreCrystalThorium => "ore-crystal-thorium",
- OreWallThorium => "ore-wall-thorium",
- OreWallBeryllium => "ore-wall-beryllium",
- GraphiticWall => "graphitic-wall",
- OreWallTungsten => "ore-wall-tungsten",
- GraphitePress => "graphite-press",
- MultiPress => "multi-press",
- SiliconSmelter => "silicon-smelter",
- SiliconCrucible => "silicon-crucible",
- Kiln => "kiln",
- PlastaniumCompressor => "plastanium-compressor",
- PhaseWeaver => "phase-weaver",
- SurgeSmelter => "surge-smelter",
- CryofluidMixer => "cryofluid-mixer",
- PyratiteMixer => "pyratite-mixer",
- BlastMixer => "blast-mixer",
- Melter => "melter",
- Separator => "separator",
- Disassembler => "disassembler",
- SporePress => "spore-press",
- Pulverizer => "pulverizer",
- CoalCentrifuge => "coal-centrifuge",
- Incinerator => "incinerator",
- SiliconArcFurnace => "silicon-arc-furnace",
- Electrolyzer => "electrolyzer",
- AtmosphericConcentrator => "atmospheric-concentrator",
- OxidationChamber => "oxidation-chamber",
- ElectricHeater => "electric-heater",
- SlagHeater => "slag-heater",
- PhaseHeater => "phase-heater",
- HeatRedirector => "heat-redirector",
- HeatRouter => "heat-router",
- SlagIncinerator => "slag-incinerator",
- CarbideCrucible => "carbide-crucible",
- SlagCentrifuge => "slag-centrifuge",
- SurgeCrucible => "surge-crucible",
- CyanogenSynthesizer => "cyanogen-synthesizer",
- PhaseSynthesizer => "phase-synthesizer",
- HeatReactor => "heat-reactor",
- CopperWall => "copper-wall",
- CopperWallLarge => "copper-wall-large",
- TitaniumWall => "titanium-wall",
- TitaniumWallLarge => "titanium-wall-large",
- PlastaniumWall => "plastanium-wall",
- PlastaniumWallLarge => "plastanium-wall-large",
- ThoriumWall => "thorium-wall",
- ThoriumWallLarge => "thorium-wall-large",
- PhaseWall => "phase-wall",
- PhaseWallLarge => "phase-wall-large",
- SurgeWall => "surge-wall",
- SurgeWallLarge => "surge-wall-large",
- Door => "door",
- DoorLarge => "door-large",
- ScrapWall => "scrap-wall",
- ScrapWallLarge => "scrap-wall-large",
- ScrapWallHuge => "scrap-wall-huge",
- ScrapWallGigantic => "scrap-wall-gigantic",
- Thruster => "thruster",
- BerylliumWall => "beryllium-wall",
- BerylliumWallLarge => "beryllium-wall-large",
- TungstenWall => "tungsten-wall",
- TungstenWallLarge => "tungsten-wall-large",
- BlastDoor => "blast-door",
- ReinforcedSurgeWall => "reinforced-surge-wall",
- ReinforcedSurgeWallLarge => "reinforced-surge-wall-large",
- CarbideWall => "carbide-wall",
- CarbideWallLarge => "carbide-wall-large",
- ShieldedWall => "shielded-wall",
- Mender => "mender",
- MendProjector => "mend-projector",
- OverdriveProjector => "overdrive-projector",
- OverdriveDome => "overdrive-dome",
- ForceProjector => "force-projector",
- ShockMine => "shock-mine",
- Radar => "radar",
- BuildTower => "build-tower",
- RegenProjector => "regen-projector",
- ShockwaveTower => "shockwave-tower",
- ShieldProjector => "shield-projector",
- LargeShieldProjector => "large-shield-projector",
- Conveyor => "conveyor",
- TitaniumConveyor => "titanium-conveyor",
- PlastaniumConveyor => "plastanium-conveyor",
- ArmoredConveyor => "armored-conveyor",
- Junction => "junction",
- BridgeConveyor => "bridge-conveyor",
- PhaseConveyor => "phase-conveyor",
- Sorter => "sorter",
- InvertedSorter => "inverted-sorter",
- Router => "router",
- Distributor => "distributor",
- OverflowGate => "overflow-gate",
- UnderflowGate => "underflow-gate",
- MassDriver => "mass-driver",
- Duct => "duct",
- ArmoredDuct => "armored-duct",
- DuctRouter => "duct-router",
- OverflowDuct => "overflow-duct",
- UnderflowDuct => "underflow-duct",
- DuctBridge => "duct-bridge",
- DuctUnloader => "duct-unloader",
- SurgeConveyor => "surge-conveyor",
- SurgeRouter => "surge-router",
- UnitCargoLoader => "unit-cargo-loader",
- UnitCargoUnloadPoint => "unit-cargo-unload-point",
- MechanicalPump => "mechanical-pump",
- RotaryPump => "rotary-pump",
- ImpulsePump => "impulse-pump",
- Conduit => "conduit",
- PulseConduit => "pulse-conduit",
- PlatedConduit => "plated-conduit",
- LiquidRouter => "liquid-router",
- LiquidContainer => "liquid-container",
- LiquidTank => "liquid-tank",
- LiquidJunction => "liquid-junction",
- BridgeConduit => "bridge-conduit",
- PhaseConduit => "phase-conduit",
- ReinforcedPump => "reinforced-pump",
- ReinforcedConduit => "reinforced-conduit",
- ReinforcedLiquidJunction => "reinforced-liquid-junction",
- ReinforcedBridgeConduit => "reinforced-bridge-conduit",
- ReinforcedLiquidRouter => "reinforced-liquid-router",
- ReinforcedLiquidContainer => "reinforced-liquid-container",
- ReinforcedLiquidTank => "reinforced-liquid-tank",
- PowerNode => "power-node",
- PowerNodeLarge => "power-node-large",
- SurgeTower => "surge-tower",
- Diode => "diode",
- Battery => "battery",
- BatteryLarge => "battery-large",
- CombustionGenerator => "combustion-generator",
- ThermalGenerator => "thermal-generator",
- SteamGenerator => "steam-generator",
- DifferentialGenerator => "differential-generator",
- RtgGenerator => "rtg-generator",
- SolarPanel => "solar-panel",
- SolarPanelLarge => "solar-panel-large",
- ThoriumReactor => "thorium-reactor",
- ImpactReactor => "impact-reactor",
- BeamNode => "beam-node",
- BeamTower => "beam-tower",
- BeamLink => "beam-link",
- TurbineCondenser => "turbine-condenser",
- ChemicalCombustionChamber => "chemical-combustion-chamber",
- PyrolysisGenerator => "pyrolysis-generator",
- FluxReactor => "flux-reactor",
- NeoplasiaReactor => "neoplasia-reactor",
- MechanicalDrill => "mechanical-drill",
- PneumaticDrill => "pneumatic-drill",
- LaserDrill => "laser-drill",
- BlastDrill => "blast-drill",
- WaterExtractor => "water-extractor",
- Cultivator => "cultivator",
- OilExtractor => "oil-extractor",
- VentCondenser => "vent-condenser",
- CliffCrusher => "cliff-crusher",
- PlasmaBore => "plasma-bore",
- LargePlasmaBore => "large-plasma-bore",
- ImpactDrill => "impact-drill",
- EruptionDrill => "eruption-drill",
- CoreShard => "core-shard",
- CoreFoundation => "core-foundation",
- CoreNucleus => "core-nucleus",
- CoreBastion => "core-bastion",
- CoreCitadel => "core-citadel",
- CoreAcropolis => "core-acropolis",
- Container => "container",
- Vault => "vault",
- Unloader => "unloader",
- ReinforcedContainer => "reinforced-container",
- ReinforcedVault => "reinforced-vault",
- Duo => "duo",
- Scatter => "scatter",
- Scorch => "scorch",
- Hail => "hail",
- Wave => "wave",
- Lancer => "lancer",
- Arc => "arc",
- Parallax => "parallax",
- Swarmer => "swarmer",
- Salvo => "salvo",
- Segment => "segment",
- Tsunami => "tsunami",
- Fuse => "fuse",
- Ripple => "ripple",
- Cyclone => "cyclone",
- Foreshadow => "foreshadow",
- Spectre => "spectre",
- Meltdown => "meltdown",
- Breach => "breach",
- Diffuse => "diffuse",
- Sublimate => "sublimate",
- Titan => "titan",
- Disperse => "disperse",
- Afflict => "afflict",
- Lustre => "lustre",
- Scathe => "scathe",
- Smite => "smite",
- Malign => "malign",
- GroundFactory => "ground-factory",
- AirFactory => "air-factory",
- NavalFactory => "naval-factory",
- AdditiveReconstructor => "additive-reconstructor",
- MultiplicativeReconstructor => "multiplicative-reconstructor",
- ExponentialReconstructor => "exponential-reconstructor",
- TetrativeReconstructor => "tetrative-reconstructor",
- RepairPoint => "repair-point",
- RepairTurret => "repair-turret",
- TankFabricator => "tank-fabricator",
- ShipFabricator => "ship-fabricator",
- MechFabricator => "mech-fabricator",
- TankRefabricator => "tank-refabricator",
- MechRefabricator => "mech-refabricator",
- ShipRefabricator => "ship-refabricator",
- PrimeRefabricator => "prime-refabricator",
- TankAssembler => "tank-assembler",
- ShipAssembler => "ship-assembler",
- MechAssembler => "mech-assembler",
- BasicAssemblerModule => "basic-assembler-module",
- UnitRepairTower => "unit-repair-tower",
- PayloadConveyor => "payload-conveyor",
- PayloadRouter => "payload-router",
- ReinforcedPayloadConveyor => "reinforced-payload-conveyor",
- ReinforcedPayloadRouter => "reinforced-payload-router",
- PayloadMassDriver => "payload-mass-driver",
- LargePayloadMassDriver => "large-payload-mass-driver",
- SmallDeconstructor => "small-deconstructor",
- Deconstructor => "deconstructor",
- Constructor => "constructor",
- LargeConstructor => "large-constructor",
- PayloadLoader => "payload-loader",
- PayloadUnloader => "payload-unloader",
- PowerSource => "power-source",
- PowerVoid => "power-void",
- ItemSource => "item-source",
- ItemVoid => "item-void",
- LiquidSource => "liquid-source",
- LiquidVoid => "liquid-void",
- PayloadSource => "payload-source",
- PayloadVoid => "payload-void",
- HeatSource => "heat-source",
- Illuminator => "illuminator",
- LegacyMechPad => "legacy-mech-pad",
- LegacyUnitFactory => "legacy-unit-factory",
- LegacyUnitFactoryAir => "legacy-unit-factory-air",
- LegacyUnitFactoryGround => "legacy-unit-factory-ground",
- CommandCenter => "command-center",
- LaunchPad => "launch-pad",
- InterplanetaryAccelerator => "interplanetary-accelerator",
- Message => "message",
- Switch => "switch",
- MicroProcessor => "micro-processor",
- LogicProcessor => "logic-processor",
- HyperProcessor => "hyper-processor",
- MemoryCell => "memory-cell",
- MemoryBank => "memory-bank",
- LogicDisplay => "logic-display",
- LargeLogicDisplay => "large-logic-display",
- Canvas => "canvas",
- ReinforcedMessage => "reinforced-message",
- WorldProcessor => "world-processor",
- WorldCell => "world-cell",
- WorldMessage => "world-message",
- }
+content_enum! {
+ pub enum Type / Block for u16 | TryFromU16Error
+ {
+ "air",
+ "spawn",
+ "cliff",
+ "build1",
+ "build2",
+ "build3",
+ "build4",
+ "build5",
+ "build6",
+ "build7",
+ "build8",
+ "build9",
+ "build10",
+ "build11",
+ "build12",
+ "build13",
+ "build14",
+ "build15",
+ "build16",
+ "deep-water",
+ "shallow-water",
+ "tainted-water",
+ "deep-tainted-water",
+ "darksand-tainted-water",
+ "sand-water",
+ "darksand-water",
+ "tar",
+ "pooled-cryofluid",
+ "molten-slag",
+ "space",
+ "empty",
+ "stone",
+ "crater-stone",
+ "char",
+ "basalt",
+ "hotrock",
+ "magmarock",
+ "sand-floor",
+ "darksand",
+ "dirt",
+ "mud",
+ "dacite",
+ "rhyolite",
+ "rhyolite-crater",
+ "rough-rhyolite",
+ "regolith",
+ "yellow-stone",
+ "carbon-stone",
+ "ferric-stone",
+ "ferric-craters",
+ "beryllic-stone",
+ "crystalline-stone",
+ "crystal-floor",
+ "yellow-stone-plates",
+ "red-stone",
+ "dense-red-stone",
+ "red-ice",
+ "arkycite-floor",
+ "arkyic-stone",
+ "rhyolite-vent",
+ "carbon-vent",
+ "arkyic-vent",
+ "yellow-stone-vent",
+ "red-stone-vent",
+ "crystalline-vent",
+ "redmat",
+ "bluemat",
+ "grass",
+ "salt",
+ "snow",
+ "ice",
+ "ice-snow",
+ "shale",
+ "moss",
+ "core-zone",
+ "spore-moss",
+ "stone-wall",
+ "spore-wall",
+ "dirt-wall",
+ "dacite-wall",
+ "ice-wall",
+ "snow-wall",
+ "dune-wall",
+ "regolith-wall",
+ "yellow-stone-wall",
+ "rhyolite-wall",
+ "carbon-wall",
+ "ferric-stone-wall",
+ "beryllic-stone-wall",
+ "arkyic-wall",
+ "crystalline-stone-wall",
+ "red-ice-wall",
+ "red-stone-wall",
+ "red-diamond-wall",
+ "sand-wall",
+ "salt-wall",
+ "shrubs",
+ "shale-wall",
+ "spore-pine",
+ "snow-pine",
+ "pine",
+ "white-tree-dead",
+ "white-tree",
+ "spore-cluster",
+ "redweed",
+ "pur-bush",
+ "yellowcoral",
+ "boulder",
+ "snow-boulder",
+ "shale-boulder",
+ "sand-boulder",
+ "dacite-boulder",
+ "basalt-boulder",
+ "carbon-boulder",
+ "ferric-boulder",
+ "beryllic-boulder",
+ "yellow-stone-boulder",
+ "arkyic-boulder",
+ "crystal-cluster",
+ "vibrant-crystal-cluster",
+ "crystal-blocks",
+ "crystal-orbs",
+ "crystalline-boulder",
+ "red-ice-boulder",
+ "rhyolite-boulder",
+ "red-stone-boulder",
+ "metal-floor",
+ "metal-floor-damaged",
+ "metal-floor-2",
+ "metal-floor-3",
+ "metal-floor-4",
+ "metal-floor-5",
+ "dark-panel-1",
+ "dark-panel-2",
+ "dark-panel-3",
+ "dark-panel-4",
+ "dark-panel-5",
+ "dark-panel-6",
+ "dark-metal",
+ "pebbles",
+ "tendrils",
+ "ore-copper",
+ "ore-lead",
+ "ore-scrap",
+ "ore-coal",
+ "ore-titanium",
+ "ore-thorium",
+ "ore-beryllium",
+ "ore-tungsten",
+ "ore-crystal-thorium",
+ "ore-wall-thorium",
+ "ore-wall-beryllium",
+ "graphitic-wall",
+ "ore-wall-tungsten",
+ "graphite-press",
+ "multi-press",
+ "silicon-smelter",
+ "silicon-crucible",
+ "kiln",
+ "plastanium-compressor",
+ "phase-weaver",
+ "surge-smelter",
+ "cryofluid-mixer",
+ "pyratite-mixer",
+ "blast-mixer",
+ "melter",
+ "separator",
+ "disassembler",
+ "spore-press",
+ "pulverizer",
+ "coal-centrifuge",
+ "incinerator",
+ "silicon-arc-furnace",
+ "electrolyzer",
+ "atmospheric-concentrator",
+ "oxidation-chamber",
+ "electric-heater",
+ "slag-heater",
+ "phase-heater",
+ "heat-redirector",
+ "heat-router",
+ "slag-incinerator",
+ "carbide-crucible",
+ "slag-centrifuge",
+ "surge-crucible",
+ "cyanogen-synthesizer",
+ "phase-synthesizer",
+ "heat-reactor",
+ "copper-wall",
+ "copper-wall-large",
+ "titanium-wall",
+ "titanium-wall-large",
+ "plastanium-wall",
+ "plastanium-wall-large",
+ "thorium-wall",
+ "thorium-wall-large",
+ "phase-wall",
+ "phase-wall-large",
+ "surge-wall",
+ "surge-wall-large",
+ "door",
+ "door-large",
+ "scrap-wall",
+ "scrap-wall-large",
+ "scrap-wall-huge",
+ "scrap-wall-gigantic",
+ "thruster",
+ "beryllium-wall",
+ "beryllium-wall-large",
+ "tungsten-wall",
+ "tungsten-wall-large",
+ "blast-door",
+ "reinforced-surge-wall",
+ "reinforced-surge-wall-large",
+ "carbide-wall",
+ "carbide-wall-large",
+ "shielded-wall",
+ "mender",
+ "mend-projector",
+ "overdrive-projector",
+ "overdrive-dome",
+ "force-projector",
+ "shock-mine",
+ "radar",
+ "build-tower",
+ "regen-projector",
+ "shockwave-tower",
+ "shield-projector",
+ "large-shield-projector",
+ "conveyor",
+ "titanium-conveyor",
+ "plastanium-conveyor",
+ "armored-conveyor",
+ "junction",
+ "bridge-conveyor",
+ "phase-conveyor",
+ "sorter",
+ "inverted-sorter",
+ "router",
+ "distributor",
+ "overflow-gate",
+ "underflow-gate",
+ "mass-driver",
+ "duct",
+ "armored-duct",
+ "duct-router",
+ "overflow-duct",
+ "underflow-duct",
+ "duct-bridge",
+ "duct-unloader",
+ "surge-conveyor",
+ "surge-router",
+ "unit-cargo-loader",
+ "unit-cargo-unload-point",
+ "mechanical-pump",
+ "rotary-pump",
+ "impulse-pump",
+ "conduit",
+ "pulse-conduit",
+ "plated-conduit",
+ "liquid-router",
+ "liquid-container",
+ "liquid-tank",
+ "liquid-junction",
+ "bridge-conduit",
+ "phase-conduit",
+ "reinforced-pump",
+ "reinforced-conduit",
+ "reinforced-liquid-junction",
+ "reinforced-bridge-conduit",
+ "reinforced-liquid-router",
+ "reinforced-liquid-container",
+ "reinforced-liquid-tank",
+ "power-node",
+ "power-node-large",
+ "surge-tower",
+ "diode",
+ "battery",
+ "battery-large",
+ "combustion-generator",
+ "thermal-generator",
+ "steam-generator",
+ "differential-generator",
+ "rtg-generator",
+ "solar-panel",
+ "solar-panel-large",
+ "thorium-reactor",
+ "impact-reactor",
+ "beam-node",
+ "beam-tower",
+ "beam-link",
+ "turbine-condenser",
+ "chemical-combustion-chamber",
+ "pyrolysis-generator",
+ "flux-reactor",
+ "neoplasia-reactor",
+ "mechanical-drill",
+ "pneumatic-drill",
+ "laser-drill",
+ "blast-drill",
+ "water-extractor",
+ "cultivator",
+ "oil-extractor",
+ "vent-condenser",
+ "cliff-crusher",
+ "plasma-bore",
+ "large-plasma-bore",
+ "impact-drill",
+ "eruption-drill",
+ "core-shard",
+ "core-foundation",
+ "core-nucleus",
+ "core-bastion",
+ "core-citadel",
+ "core-acropolis",
+ "container",
+ "vault",
+ "unloader",
+ "reinforced-container",
+ "reinforced-vault",
+ "duo",
+ "scatter",
+ "scorch",
+ "hail",
+ "wave",
+ "lancer",
+ "arc",
+ "parallax",
+ "swarmer",
+ "salvo",
+ "segment",
+ "tsunami",
+ "fuse",
+ "ripple",
+ "cyclone",
+ "foreshadow",
+ "spectre",
+ "meltdown",
+ "breach",
+ "diffuse",
+ "sublimate",
+ "titan",
+ "disperse",
+ "afflict",
+ "lustre",
+ "scathe",
+ "smite",
+ "malign",
+ "ground-factory",
+ "air-factory",
+ "naval-factory",
+ "additive-reconstructor",
+ "multiplicative-reconstructor",
+ "exponential-reconstructor",
+ "tetrative-reconstructor",
+ "repair-point",
+ "repair-turret",
+ "tank-fabricator",
+ "ship-fabricator",
+ "mech-fabricator",
+ "tank-refabricator",
+ "mech-refabricator",
+ "ship-refabricator",
+ "prime-refabricator",
+ "tank-assembler",
+ "ship-assembler",
+ "mech-assembler",
+ "basic-assembler-module",
+ "unit-repair-tower",
+ "payload-conveyor",
+ "payload-router",
+ "reinforced-payload-conveyor",
+ "reinforced-payload-router",
+ "payload-mass-driver",
+ "large-payload-mass-driver",
+ "small-deconstructor",
+ "deconstructor",
+ "constructor",
+ "large-constructor",
+ "payload-loader",
+ "payload-unloader",
+ "power-source",
+ "power-void",
+ "item-source",
+ "item-void",
+ "liquid-source",
+ "liquid-void",
+ "payload-source",
+ "payload-void",
+ "heat-source",
+ "illuminator",
+ "legacy-mech-pad",
+ "legacy-unit-factory",
+ "legacy-unit-factory-air",
+ "legacy-unit-factory-ground",
+ "command-center",
+ "launch-pad",
+ "interplanetary-accelerator",
+ "message",
+ "switch",
+ "micro-processor",
+ "logic-processor",
+ "hyper-processor",
+ "memory-cell",
+ "memory-bank",
+ "logic-display",
+ "large-logic-display",
+ "canvas",
+ "reinforced-message",
+ "world-processor",
+ "world-cell",
+ "world-message",
+ }
}
diff --git a/src/block/defense.rs b/src/block/defense.rs
index 48de13c..2f7eb46 100644
--- a/src/block/defense.rs
+++ b/src/block/defense.rs
@@ -1,113 +1,102 @@
use std::any::Any;
-use crate::block::{BlockLogic, DataConvertError, DeserializeError, make_register, SerializeError};
-use crate::block::simple::{BuildCost, cost, SimpleBlock, state_impl};
-use crate::data::GridPos;
+use crate::block::simple::{cost, state_impl, BuildCost, SimpleBlock};
+use crate::block::{make_register, BlockLogic, DataConvertError, DeserializeError, SerializeError};
use crate::data::dynamic::{DynData, DynType};
+use crate::data::GridPos;
use crate::item::storage::Storage;
make_register!
(
- COPPER_WALL: "copper-wall" => SimpleBlock::new(1, true, cost!(Copper: 6));
- COPPER_WALL_LARGE: "copper-wall-large" => SimpleBlock::new(2, true, cost!(Copper: 24));
- TITANIUM_WALL: "titanium-wall" => SimpleBlock::new(1, true, cost!(Titanium: 6));
- TITANIUM_WALL_LARGE: "titanium-wall-large" => SimpleBlock::new(2, true, cost!(Titanium: 24));
- PLASTANIUM_WALL: "plastanium-wall" => SimpleBlock::new(1, true, cost!(Metaglass: 2, Plastanium: 5));
- PLASTANIUM_WALL_LARGE: "plastanium-wall-large" => SimpleBlock::new(2, true, cost!(Metaglass: 8, Plastanium: 20));
- THORIUM_WALL: "thorium-wall" => SimpleBlock::new(1, true, cost!(Thorium: 6));
- THORIUM_WALL_LARGE: "thorium-wall-large" => SimpleBlock::new(2, true, cost!(Thorium: 24));
- PHASE_WALL: "phase-wall" => SimpleBlock::new(1, true, cost!(PhaseFabric: 6));
- PHASE_WALL_LARGE: "phase-wall-large" => SimpleBlock::new(2, true, cost!(PhaseFabric: 24));
- SURGE_WALL: "surge-wall" => SimpleBlock::new(1, true, cost!(SurgeAlloy: 6));
- SURGE_WALL_LARGE: "surge-wall-large" => SimpleBlock::new(2, true, cost!(SurgeAlloy: 24));
- DOOR: "door" => DoorBlock::new(1, true, cost!(Titanium: 6, Silicon: 4));
- DOOR_LARGE: "door-large" => DoorBlock::new(2, true, cost!(Titanium: 24, Silicon: 16));
- // sandbox only
- SCRAP_WALL: "scrap-wall" => SimpleBlock::new(1, true, cost!(Scrap: 6));
- SCRAP_WALL_LARGE: "scrap-wall-large" => SimpleBlock::new(2, true, cost!(Scrap: 24));
- SCRAP_WALL_HUGE: "scrap-wall-huge" => SimpleBlock::new(3, true, cost!(Scrap: 54));
- SCRAP_WALL_GIGANTIC: "scrap-wall-gigantic" => SimpleBlock::new(4, true, cost!(Scrap: 96));
- THRUSTER: "thruster" => SimpleBlock::new(4, false, cost!(Scrap: 96));
+ "copper-wall" => SimpleBlock::new(1, true, cost!(Copper: 6));
+ "copper-wall-large" => SimpleBlock::new(2, true, cost!(Copper: 24));
+ "titanium-wall" => SimpleBlock::new(1, true, cost!(Titanium: 6));
+ "titanium-wall-large" => SimpleBlock::new(2, true, cost!(Titanium: 24));
+ "plastanium-wall" => SimpleBlock::new(1, true, cost!(Metaglass: 2, Plastanium: 5));
+ "plastanium-wall-large" => SimpleBlock::new(2, true, cost!(Metaglass: 8, Plastanium: 20));
+ "thorium-wall" => SimpleBlock::new(1, true, cost!(Thorium: 6));
+ "thorium-wall-large" => SimpleBlock::new(2, true, cost!(Thorium: 24));
+ "phase-wall" => SimpleBlock::new(1, true, cost!(PhaseFabric: 6));
+ "phase-wall-large" => SimpleBlock::new(2, true, cost!(PhaseFabric: 24));
+ "surge-wall" => SimpleBlock::new(1, true, cost!(SurgeAlloy: 6));
+ "surge-wall-large" => SimpleBlock::new(2, true, cost!(SurgeAlloy: 24));
+ "door" => DoorBlock::new(1, true, cost!(Titanium: 6, Silicon: 4));
+ "door-large" => DoorBlock::new(2, true, cost!(Titanium: 24, Silicon: 16));
+ // sandbox only
+ "scrap-wall" => SimpleBlock::new(1, true, cost!(Scrap: 6));
+ "scrap-wall-large" => SimpleBlock::new(2, true, cost!(Scrap: 24));
+ "scrap-wall-huge" => SimpleBlock::new(3, true, cost!(Scrap: 54));
+ "scrap-wall-gigantic" => SimpleBlock::new(4, true, cost!(Scrap: 96));
+ "thruster" => SimpleBlock::new(4, false, cost!(Scrap: 96));
);
-pub struct DoorBlock
-{
- size: u8,
- symmetric: bool,
- build_cost: BuildCost,
+pub struct DoorBlock {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
}
-impl DoorBlock
-{
- pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self
- {
- if size == 0
- {
- panic!("invalid size");
- }
- Self{size, symmetric, build_cost}
- }
-
- state_impl!(pub bool);
+impl DoorBlock {
+ pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self {
+ if size == 0 {
+ panic!("invalid size");
+ }
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ }
+ }
+
+ state_impl!(pub bool);
}
-impl BlockLogic for DoorBlock
-{
- fn get_size(&self) -> u8
- {
- self.size
- }
-
- fn is_symmetric(&self) -> bool
- {
- self.symmetric
- }
-
- fn create_build_cost(&self) -> Option<Storage>
- {
- if !self.build_cost.is_empty()
- {
- let mut storage = Storage::new();
- for (ty, cnt) in self.build_cost
- {
- storage.add(*ty, *cnt, u32::MAX);
- }
- Some(storage)
- }
- else {None}
- }
-
- fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError>
- {
- Ok(DynData::Boolean(false))
- }
-
- fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError>
- {
- match data
- {
- DynData::Boolean(opened) => Ok(Some(Self::create_state(opened))),
- _ => Err(DeserializeError::InvalidType{have: data.get_type(), expect: DynType::Boolean}),
- }
- }
-
- fn clone_state(&self, state: &dyn Any) -> Box<dyn Any>
- {
- let state = Self::get_state(state);
- Box::new(Self::create_state(*state))
- }
-
- fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool)
- {
- }
-
- fn rotate_state(&self, _: &mut dyn Any, _: bool)
- {
- }
-
- fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError>
- {
- let state = Self::get_state(state);
- Ok(DynData::Boolean(*state))
- }
+impl BlockLogic for DoorBlock {
+ fn get_size(&self) -> u8 {
+ self.size
+ }
+
+ fn is_symmetric(&self) -> bool {
+ self.symmetric
+ }
+
+ fn create_build_cost(&self) -> Option<Storage> {
+ if !self.build_cost.is_empty() {
+ let mut storage = Storage::new();
+ for (ty, cnt) in self.build_cost {
+ storage.add(*ty, *cnt, u32::MAX);
+ }
+ Some(storage)
+ } else {
+ None
+ }
+ }
+
+ fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ Ok(DynData::Boolean(false))
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> {
+ match data {
+ DynData::Boolean(opened) => Ok(Some(Self::create_state(opened))),
+ _ => Err(DeserializeError::InvalidType {
+ have: data.get_type(),
+ expect: DynType::Boolean,
+ }),
+ }
+ }
+
+ fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> {
+ let state = Self::get_state(state);
+ Box::new(Self::create_state(*state))
+ }
+
+ fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool) {}
+
+ fn rotate_state(&self, _: &mut dyn Any, _: bool) {}
+
+ fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> {
+ let state = Self::get_state(state);
+ Ok(DynData::Boolean(*state))
+ }
}
diff --git a/src/block/extraction.rs b/src/block/extraction.rs
index b036081..237af77 100644
--- a/src/block/extraction.rs
+++ b/src/block/extraction.rs
@@ -3,11 +3,11 @@ use crate::block::simple::{cost, SimpleBlock};
make_register!
(
- MECHANICAL_DRILL: "mechanical-drill" => SimpleBlock::new(2, true, cost!(Copper: 12));
- PNEUMATIC_DRILL: "pneumatic-drill" => SimpleBlock::new(2, true, cost!(Copper: 18, Graphite: 10));
- LASER_DRILL: "laser-drill" => SimpleBlock::new(3, true, cost!(Copper: 35, Graphite: 30, Titanium: 20, Silicon: 30));
- BLAST_DRILL: "blast-drill" => SimpleBlock::new(4, true, cost!(Copper: 65, Titanium: 50, Thorium: 75, Silicon: 60));
- WATER_EXTRACTOR: "water-extractor" => SimpleBlock::new(2, true, cost!(Copper: 30, Lead: 30, Metaglass: 30, Graphite: 30));
- CULTIVATOR: "cultivator" => SimpleBlock::new(2, true, cost!(Copper: 25, Lead: 25, Silicon: 10));
- OIL_EXTRACTOR: "oil-extractor" => SimpleBlock::new(3, true, cost!(Copper: 150, Lead: 115, Graphite: 175, Thorium: 115, Silicon: 75));
+ "mechanical-drill" => SimpleBlock::new(2, true, cost!(Copper: 12));
+ "pneumatic-drill" => SimpleBlock::new(2, true, cost!(Copper: 18, Graphite: 10));
+ "laser-drill" => SimpleBlock::new(3, true, cost!(Copper: 35, Graphite: 30, Titanium: 20, Silicon: 30));
+ "blast-drill" => SimpleBlock::new(4, true, cost!(Copper: 65, Titanium: 50, Thorium: 75, Silicon: 60));
+ "water-extractor" => SimpleBlock::new(2, true, cost!(Copper: 30, Lead: 30, Metaglass: 30, Graphite: 30));
+ "cultivator" => SimpleBlock::new(2, true, cost!(Copper: 25, Lead: 25, Silicon: 10));
+ "oil-extractor" => SimpleBlock::new(3, true, cost!(Copper: 150, Lead: 115, Graphite: 175, Thorium: 115, Silicon: 75));
);
diff --git a/src/block/factory.rs b/src/block/factory.rs
index 9374140..add4458 100644
--- a/src/block/factory.rs
+++ b/src/block/factory.rs
@@ -3,24 +3,24 @@ use crate::block::simple::{cost, SimpleBlock};
make_register!
(
- GRAPHITE_PRESS: "graphite-press" => SimpleBlock::new(2, true, cost!(Copper: 75, Lead: 30));
- MULTI_PRESS: "multi-press" => SimpleBlock::new(3, true, cost!(Lead: 100, Graphite: 50, Titanium: 100, Silicon: 25));
- SILICON_SMELTER: "silicon-smelter" => SimpleBlock::new(2, true, cost!(Copper: 30, Lead: 25));
- SILICON_CRUCIBLE: "silicon-crucible" => SimpleBlock::new(3, true, cost!(Metaglass: 80, Titanium: 120, Silicon: 60, Plastanium: 35));
- KILN: "kiln" => SimpleBlock::new(2, true, cost!(Copper: 60, Lead: 30, Graphite: 30));
- PLASTANIUM_COMPRESSOR: "plastanium-compressor" => SimpleBlock::new(2, true, cost!(Lead: 115, Graphite: 60, Titanium: 80, Silicon: 80));
- PHASE_WEAVER: "phase-weaver" => SimpleBlock::new(2, true, cost!(Lead: 120, Thorium: 75, Silicon: 130));
- SURGE_SMELTER: "surge-smelter" => SimpleBlock::new(3, true, cost!(Lead: 80, Thorium: 70, Silicon: 80));
- CRYOFLUID_MIXER: "cryofluid-mixer" => SimpleBlock::new(2, true, cost!(Lead: 65, Thorium: 60, Silicon: 40));
- PYRATITE_MIXER: "pyratite-mixer" => SimpleBlock::new(2, true, cost!(Copper: 50, Lead: 25));
- BLAST_MIXER: "blast-mixer" => SimpleBlock::new(2, true, cost!(Lead: 30, Thorium: 20));
- MELTER: "melter" => SimpleBlock::new(1, true, cost!(Copper: 30, Lead: 35, Graphite: 45));
- SEPARATOR: "separator" => SimpleBlock::new(2, true, cost!(Copper: 30, Titanium: 25));
- DISASSEMBLER: "disassembler" => SimpleBlock::new(3, true, cost!(Titanium: 100, Thorium: 80, Silicon: 150, Plastanium: 40));
- SPORE_PRESS: "spore-press" => SimpleBlock::new(2, true, cost!(Lead: 35, Silicon: 30));
- PULVERIZER: "pulverizer" => SimpleBlock::new(1, true, cost!(Copper: 30, Lead: 25));
- COAL_CENTRIFUGE: "coal-centrifuge" => SimpleBlock::new(2, true, cost!(Lead: 30, Graphite: 40, Titanium: 20));
- INCINERATOR: "incinerator" => SimpleBlock::new(1, true, cost!(Lead: 15, Graphite: 5));
- // sandbox only
- HEAT_SOURCE: "heat-source" => SimpleBlock::new(1, false, &[]);
+ "graphite-press" => SimpleBlock::new(2, true, cost!(Copper: 75, Lead: 30));
+ "multi-press" => SimpleBlock::new(3, true, cost!(Lead: 100, Graphite: 50, Titanium: 100, Silicon: 25));
+ "silicon-smelter" => SimpleBlock::new(2, true, cost!(Copper: 30, Lead: 25));
+ "silicon-crucible" => SimpleBlock::new(3, true, cost!(Metaglass: 80, Titanium: 120, Silicon: 60, Plastanium: 35));
+ "kiln" => SimpleBlock::new(2, true, cost!(Copper: 60, Lead: 30, Graphite: 30));
+ "plastanium-compressor" => SimpleBlock::new(2, true, cost!(Lead: 115, Graphite: 60, Titanium: 80, Silicon: 80));
+ "phase-weaver" => SimpleBlock::new(2, true, cost!(Lead: 120, Thorium: 75, Silicon: 130));
+ "surge-smelter" => SimpleBlock::new(3, true, cost!(Lead: 80, Thorium: 70, Silicon: 80));
+ "cryofluid-mixer" => SimpleBlock::new(2, true, cost!(Lead: 65, Thorium: 60, Silicon: 40));
+ "pyratite-mixer" => SimpleBlock::new(2, true, cost!(Copper: 50, Lead: 25));
+ "blast-mixer" => SimpleBlock::new(2, true, cost!(Lead: 30, Thorium: 20));
+ "melter" => SimpleBlock::new(1, true, cost!(Copper: 30, Lead: 35, Graphite: 45));
+ "separator" => SimpleBlock::new(2, true, cost!(Copper: 30, Titanium: 25));
+ "disassembler" => SimpleBlock::new(3, true, cost!(Titanium: 100, Thorium: 80, Silicon: 150, Plastanium: 40));
+ "spore-press" => SimpleBlock::new(2, true, cost!(Lead: 35, Silicon: 30));
+ "pulverizer" => SimpleBlock::new(1, true, cost!(Copper: 30, Lead: 25));
+ "coal-centrifuge" => SimpleBlock::new(2, true, cost!(Lead: 30, Graphite: 40, Titanium: 20));
+ "incinerator" => SimpleBlock::new(1, true, cost!(Lead: 15, Graphite: 5));
+ // sandbox only
+ "heat-source" => SimpleBlock::new(1, false, &[]);
);
diff --git a/src/block/fluid.rs b/src/block/fluid.rs
index 41a4eeb..55b0097 100644
--- a/src/block/fluid.rs
+++ b/src/block/fluid.rs
@@ -6,181 +6,164 @@ use crate::block::{BlockLogic, DataConvertError, DeserializeError, make_register
use crate::block::simple::{BuildCost, cost, SimpleBlock, state_impl};
use crate::block::transport::BridgeBlock;
use crate::content;
-use crate::data::GridPos;
use crate::data::dynamic::{DynData, DynType};
+use crate::data::GridPos;
use crate::fluid;
use crate::item::storage::Storage;
make_register!
(
- MECHANICAL_PUMP: "mechanical-pump" => SimpleBlock::new(1, true, cost!(Copper: 15, Metaglass: 10));
- ROTARY_PUMP: "rotary-pump" => SimpleBlock::new(2, true, cost!(Copper: 70, Metaglass: 50, Titanium: 35, Silicon: 20));
- IMPULSE_PUMP: "impulse-pump" => SimpleBlock::new(3, true, cost!(Copper: 80, Metaglass: 90, Titanium: 40, Thorium: 35, Silicon: 30));
- CONDUIT: "conduit" => SimpleBlock::new(1, false, cost!(Metaglass: 1));
- PULSE_CONDUIT: "pulse-conduit" => SimpleBlock::new(1, false, cost!(Metaglass: 1, Titanium: 2));
- PLATED_CONDUIT: "plated-conduit" => SimpleBlock::new(1, false, cost!(Metaglass: 1, Thorium: 2, Plastanium: 1));
- LIQUID_ROUTER: "liquid-router" => SimpleBlock::new(1, true, cost!(Metaglass: 2, Graphite: 4));
- LIQUID_CONTAINER: "liquid-container" => SimpleBlock::new(2, true, cost!(Metaglass: 15, Titanium: 10));
- LIQUID_TANK: "liquid-tank" => SimpleBlock::new(3, true, cost!(Metaglass: 40, Titanium: 30));
- LIQUID_JUNCTION: "liquid-junction" => SimpleBlock::new(1, true, cost!(Metaglass: 8, Graphite: 4));
- BRIDGE_CONDUIT: "bridge-conduit" => BridgeBlock::new(1, true, cost!(Metaglass: 8, Graphite: 4), 4, true);
- PHASE_CONDUIT: "phase-conduit" => BridgeBlock::new(1, true, cost!(Metaglass: 20, Titanium: 10, Silicon: 7, PhaseFabric: 5), 12, true);
- // sandbox only
- LIQUID_SOURCE: "liquid-source" => FluidBlock::new(1, true, &[]);
- LIQUID_VOID: "liquid-void" => SimpleBlock::new(1, true, &[]);
+ "mechanical-pump" => SimpleBlock::new(1, true, cost!(Copper: 15, Metaglass: 10));
+ "rotary-pump" => SimpleBlock::new(2, true, cost!(Copper: 70, Metaglass: 50, Titanium: 35, Silicon: 20));
+ "impulse-pump" => SimpleBlock::new(3, true, cost!(Copper: 80, Metaglass: 90, Titanium: 40, Thorium: 35, Silicon: 30));
+ "conduit" => SimpleBlock::new(1, false, cost!(Metaglass: 1));
+ "pulse-conduit" => SimpleBlock::new(1, false, cost!(Metaglass: 1, Titanium: 2));
+ "plated-conduit" => SimpleBlock::new(1, false, cost!(Metaglass: 1, Thorium: 2, Plastanium: 1));
+ "liquid-router" => SimpleBlock::new(1, true, cost!(Metaglass: 2, Graphite: 4));
+ "liquid-container" => SimpleBlock::new(2, true, cost!(Metaglass: 15, Titanium: 10));
+ "liquid-tank" => SimpleBlock::new(3, true, cost!(Metaglass: 40, Titanium: 30));
+ "liquid-junction" => SimpleBlock::new(1, true, cost!(Metaglass: 8, Graphite: 4));
+ "bridge-conduit" => BridgeBlock::new(1, true, cost!(Metaglass: 8, Graphite: 4), 4, true);
+ "phase-conduit" => BridgeBlock::new(1, true, cost!(Metaglass: 20, Titanium: 10, Silicon: 7, PhaseFabric: 5), 12, true);
+ // sandbox only
+ "liquid-source" => FluidBlock::new(1, true, &[]);
+ "liquid-void" => SimpleBlock::new(1, true, &[]);
);
-pub struct FluidBlock
-{
- size: u8,
- symmetric: bool,
- build_cost: BuildCost,
+pub struct FluidBlock {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
}
-impl FluidBlock
-{
- pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self
- {
- if size == 0
- {
- panic!("invalid size");
- }
- Self{size, symmetric, build_cost}
- }
-
- state_impl!(pub Option<fluid::Type>);
+impl FluidBlock {
+ pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self {
+ if size == 0 {
+ panic!("invalid size");
+ }
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ }
+ }
+
+ state_impl!(pub Option<fluid::Type>);
}
-impl BlockLogic for FluidBlock
-{
- fn get_size(&self) -> u8
- {
- self.size
- }
-
- fn is_symmetric(&self) -> bool
- {
- self.symmetric
- }
-
- fn create_build_cost(&self) -> Option<Storage>
- {
- if !self.build_cost.is_empty()
- {
- let mut storage = Storage::new();
- for (ty, cnt) in self.build_cost
- {
- storage.add(*ty, *cnt, u32::MAX);
- }
- Some(storage)
- }
- else {None}
- }
-
- fn data_from_i32(&self, config: i32, _: GridPos) -> Result<DynData, DataConvertError>
- {
- if config < 0 || config > u16::MAX as i32
- {
- return Err(DataConvertError::Custom(Box::new(FluidConvertError(config))));
- }
- Ok(DynData::Content(content::Type::Fluid, config as u16))
- }
-
- fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError>
- {
- match data
- {
- DynData::Empty => Ok(Some(Self::create_state(None))),
- DynData::Content(content::Type::Fluid, id) => Ok(Some(Self::create_state(Some(FluidDeserializeError::forward(fluid::Type::try_from(id))?)))),
- DynData::Content(have, ..) => Err(DeserializeError::Custom(Box::new(FluidDeserializeError::ContentType(have)))),
- _ => Err(DeserializeError::InvalidType{have: data.get_type(), expect: DynType::Content}),
- }
- }
-
- fn clone_state(&self, state: &dyn Any) -> Box<dyn Any>
- {
- let state = Self::get_state(state);
- Box::new(Self::create_state(*state))
- }
-
- fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool)
- {
- }
-
- fn rotate_state(&self, _: &mut dyn Any, _: bool)
- {
- }
-
- fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError>
- {
- match Self::get_state(state)
- {
- None => Ok(DynData::Empty),
- Some(fluid) => Ok(DynData::Content(content::Type::Fluid, (*fluid).into())),
- }
- }
+impl BlockLogic for FluidBlock {
+ fn get_size(&self) -> u8 {
+ self.size
+ }
+
+ fn is_symmetric(&self) -> bool {
+ self.symmetric
+ }
+
+ fn create_build_cost(&self) -> Option<Storage> {
+ if !self.build_cost.is_empty() {
+ let mut storage = Storage::new();
+ for (ty, cnt) in self.build_cost {
+ storage.add(*ty, *cnt, u32::MAX);
+ }
+ Some(storage)
+ } else {
+ None
+ }
+ }
+
+ fn data_from_i32(&self, config: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ if config < 0 || config > u16::MAX as i32 {
+ return Err(DataConvertError::Custom(Box::new(FluidConvertError(
+ config,
+ ))));
+ }
+ Ok(DynData::Content(content::Type::Fluid, config as u16))
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> {
+ match data {
+ DynData::Empty => Ok(Some(Self::create_state(None))),
+ DynData::Content(content::Type::Fluid, id) => Ok(Some(Self::create_state(Some(
+ FluidDeserializeError::forward(fluid::Type::try_from(id))?,
+ )))),
+ DynData::Content(have, ..) => Err(DeserializeError::Custom(Box::new(
+ FluidDeserializeError::ContentType(have),
+ ))),
+ _ => Err(DeserializeError::InvalidType {
+ have: data.get_type(),
+ expect: DynType::Content,
+ }),
+ }
+ }
+
+ fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> {
+ let state = Self::get_state(state);
+ Box::new(Self::create_state(*state))
+ }
+
+ fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool) {}
+
+ fn rotate_state(&self, _: &mut dyn Any, _: bool) {}
+
+ fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> {
+ match Self::get_state(state) {
+ None => Ok(DynData::Empty),
+ Some(fluid) => Ok(DynData::Content(content::Type::Fluid, (*fluid).into())),
+ }
+ }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FluidConvertError(pub i32);
-impl fmt::Display for FluidConvertError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- write!(f, "invalid config ({}) for fluid", self.0)
- }
+impl fmt::Display for FluidConvertError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "invalid config ({}) for fluid", self.0)
+ }
}
impl Error for FluidConvertError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum FluidDeserializeError
-{
- ContentType(content::Type),
- NotFound(fluid::TryFromU16Error),
+pub enum FluidDeserializeError {
+ ContentType(content::Type),
+ NotFound(fluid::TryFromU16Error),
}
-impl FluidDeserializeError
-{
- pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, DeserializeError>
- {
- match result
- {
- Ok(v) => Ok(v),
- Err(e) => Err(DeserializeError::Custom(Box::new(e.into()))),
- }
- }
+impl FluidDeserializeError {
+ pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, DeserializeError> {
+ match result {
+ Ok(v) => Ok(v),
+ Err(e) => Err(DeserializeError::Custom(Box::new(e.into()))),
+ }
+ }
}
-impl From<fluid::TryFromU16Error> for FluidDeserializeError
-{
- fn from(err: fluid::TryFromU16Error) -> Self
- {
- Self::NotFound(err)
- }
+impl From<fluid::TryFromU16Error> for FluidDeserializeError {
+ fn from(err: fluid::TryFromU16Error) -> Self {
+ Self::NotFound(err)
+ }
}
-impl fmt::Display for FluidDeserializeError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::ContentType(have) => write!(f, "expected content {:?} but got {have:?}", content::Type::Fluid),
- Self::NotFound(..) => f.write_str("fluid not found"),
- }
- }
+impl fmt::Display for FluidDeserializeError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::ContentType(have) => write!(
+ f,
+ "expected content {:?} but got {have:?}",
+ content::Type::Fluid
+ ),
+ Self::NotFound(..) => f.write_str("fluid not found"),
+ }
+ }
}
-impl Error for FluidDeserializeError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::NotFound(e) => Some(e),
- _ => None,
- }
- }
+impl Error for FluidDeserializeError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::NotFound(e) => Some(e),
+ _ => None,
+ }
+ }
}
diff --git a/src/block/logic.rs b/src/block/logic.rs
index 879c8b3..ccfd8a0 100644
--- a/src/block/logic.rs
+++ b/src/block/logic.rs
@@ -4,675 +4,614 @@ use std::error::Error;
use std::fmt;
use std::string::FromUtf8Error;
-use flate2::{Compress, CompressError, Compression, Decompress, DecompressError, FlushCompress, FlushDecompress, Status};
+use flate2::{
+ Compress, CompressError, Compression, Decompress, DecompressError, FlushCompress,
+ FlushDecompress, Status,
+};
-use crate::block::{BlockLogic, DataConvertError, DeserializeError, make_register, SerializeError};
-use crate::block::simple::{BuildCost, cost, SimpleBlock, state_impl};
-use crate::data::{self, DataRead, DataWrite, GridPos};
+use crate::block::simple::{cost, state_impl, BuildCost, SimpleBlock};
+use crate::block::{make_register, BlockLogic, DataConvertError, DeserializeError, SerializeError};
use crate::data::dynamic::{DynData, DynType};
+use crate::data::{self, DataRead, DataWrite, GridPos};
use crate::item::storage::Storage;
make_register!
(
- MESSAGE: "message" => MessageLogic::new(1, true, cost!(Copper: 5, Graphite: 5));
- SWITCH: "switch" => SwitchLogic::new(1, true, cost!(Copper: 5, Graphite: 5));
- MICRO_PROCESSOR: "micro-processor" => ProcessorLogic::new(1, true, cost!(Copper: 90, Lead: 50, Silicon: 50));
- LOGIC_PROCESSOR: "logic-processor" => ProcessorLogic::new(2, true, cost!(Lead: 320, Graphite: 60, Thorium: 50, Silicon: 80));
- HYPER_PROCESSOR: "hyper-processor" => ProcessorLogic::new(3, true, cost!(Lead: 450, Thorium: 75, Silicon: 150, SurgeAlloy: 50));
- MEMORY_CELL: "memory-cell" => SimpleBlock::new(1, true, cost!(Copper: 30, Graphite: 30, Silicon: 30));
- MEMORY_BANK: "memory-bank" => SimpleBlock::new(2, true, cost!(Copper: 30, Graphite: 80, Silicon: 80, PhaseFabric: 30));
- LOGIC_DISPLAY: "logic-display" => SimpleBlock::new(3, true, cost!(Lead: 100, Metaglass: 50, Silicon: 50));
- LARGE_LOGIC_DISPLAY: "large-logic-display" => SimpleBlock::new(6, true, cost!(Lead: 200, Metaglass: 100, Silicon: 150, PhaseFabric: 75));
+ "message" => MessageLogic::new(1, true, cost!(Copper: 5, Graphite: 5));
+ "switch" => SwitchLogic::new(1, true, cost!(Copper: 5, Graphite: 5));
+ "micro-processor" => ProcessorLogic::new(1, true, cost!(Copper: 90, Lead: 50, Silicon: 50));
+ "logic-processor" => ProcessorLogic::new(2, true, cost!(Lead: 320, Graphite: 60, Thorium: 50, Silicon: 80));
+ "hyper-processor" => ProcessorLogic::new(3, true, cost!(Lead: 450, Thorium: 75, Silicon: 150, SurgeAlloy: 50));
+ "memory-cell" => SimpleBlock::new(1, true, cost!(Copper: 30, Graphite: 30, Silicon: 30));
+ "memory-bank" => SimpleBlock::new(2, true, cost!(Copper: 30, Graphite: 80, Silicon: 80, PhaseFabric: 30));
+ "logic-display" => SimpleBlock::new(3, true, cost!(Lead: 100, Metaglass: 50, Silicon: 50));
+ "large-logic-display" => SimpleBlock::new(6, true, cost!(Lead: 200, Metaglass: 100, Silicon: 150, PhaseFabric: 75));
);
-pub struct MessageLogic
-{
- size: u8,
- symmetric: bool,
- build_cost: BuildCost,
+pub struct MessageLogic {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
}
-impl MessageLogic
-{
- pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self
- {
- if size == 0
- {
- panic!("invalid size");
- }
- Self{size, symmetric, build_cost}
- }
-
- state_impl!(pub String);
+impl MessageLogic {
+ pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self {
+ if size == 0 {
+ panic!("invalid size");
+ }
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ }
+ }
+
+ state_impl!(pub String);
}
-impl BlockLogic for MessageLogic
-{
- fn get_size(&self) -> u8
- {
- self.size
- }
-
- fn is_symmetric(&self) -> bool
- {
- self.symmetric
- }
-
- fn create_build_cost(&self) -> Option<Storage>
- {
- if !self.build_cost.is_empty()
- {
- let mut storage = Storage::new();
- for (ty, cnt) in self.build_cost
- {
- storage.add(*ty, *cnt, u32::MAX);
- }
- Some(storage)
- }
- else {None}
- }
-
- fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError>
- {
- Ok(DynData::Empty)
- }
-
- fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError>
- {
- match data
- {
- DynData::Empty | DynData::String(None) => Ok(Some(Self::create_state(String::new()))),
- DynData::String(Some(s)) => Ok(Some(Self::create_state(s))),
- _ => Err(DeserializeError::InvalidType{have: data.get_type(), expect: DynType::String}),
- }
- }
-
- fn clone_state(&self, state: &dyn Any) -> Box<dyn Any>
- {
- Box::new(Self::get_state(state).clone())
- }
-
- fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool)
- {
- }
-
- fn rotate_state(&self, _: &mut dyn Any, _: bool)
- {
- }
-
- fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError>
- {
- Ok(DynData::String(Some(Self::get_state(state).clone())))
- }
+impl BlockLogic for MessageLogic {
+ fn get_size(&self) -> u8 {
+ self.size
+ }
+
+ fn is_symmetric(&self) -> bool {
+ self.symmetric
+ }
+
+ fn create_build_cost(&self) -> Option<Storage> {
+ if !self.build_cost.is_empty() {
+ let mut storage = Storage::new();
+ for (ty, cnt) in self.build_cost {
+ storage.add(*ty, *cnt, u32::MAX);
+ }
+ Some(storage)
+ } else {
+ None
+ }
+ }
+
+ fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ Ok(DynData::Empty)
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> {
+ match data {
+ DynData::Empty | DynData::String(None) => Ok(Some(Self::create_state(String::new()))),
+ DynData::String(Some(s)) => Ok(Some(Self::create_state(s))),
+ _ => Err(DeserializeError::InvalidType {
+ have: data.get_type(),
+ expect: DynType::String,
+ }),
+ }
+ }
+
+ fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> {
+ Box::new(Self::get_state(state).clone())
+ }
+
+ fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool) {}
+
+ fn rotate_state(&self, _: &mut dyn Any, _: bool) {}
+
+ fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> {
+ Ok(DynData::String(Some(Self::get_state(state).clone())))
+ }
}
-pub struct SwitchLogic
-{
- size: u8,
- symmetric: bool,
- build_cost: BuildCost,
+pub struct SwitchLogic {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
}
-impl SwitchLogic
-{
- pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self
- {
- if size == 0
- {
- panic!("invalid size");
- }
- Self{size, symmetric, build_cost}
- }
-
- state_impl!(pub bool);
+impl SwitchLogic {
+ pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self {
+ if size == 0 {
+ panic!("invalid size");
+ }
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ }
+ }
+
+ state_impl!(pub bool);
}
-impl BlockLogic for SwitchLogic
-{
- fn get_size(&self) -> u8
- {
- self.size
- }
-
- fn is_symmetric(&self) -> bool
- {
- self.symmetric
- }
-
- fn create_build_cost(&self) -> Option<Storage>
- {
- if !self.build_cost.is_empty()
- {
- let mut storage = Storage::new();
- for (ty, cnt) in self.build_cost
- {
- storage.add(*ty, *cnt, u32::MAX);
- }
- Some(storage)
- }
- else {None}
- }
-
- fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError>
- {
- Ok(DynData::Empty)
- }
-
- fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError>
- {
- match data
- {
- DynData::Empty => Ok(Some(Self::create_state(true))),
- DynData::Boolean(enabled) => Ok(Some(Self::create_state(enabled))),
- _ => Err(DeserializeError::InvalidType{have: data.get_type(), expect: DynType::Boolean}),
- }
- }
-
- fn clone_state(&self, state: &dyn Any) -> Box<dyn Any>
- {
- Box::new(Self::get_state(state).clone())
- }
-
- fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool)
- {
- }
-
- fn rotate_state(&self, _: &mut dyn Any, _: bool)
- {
- }
-
- fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError>
- {
- Ok(DynData::Boolean(*Self::get_state(state)))
- }
+impl BlockLogic for SwitchLogic {
+ fn get_size(&self) -> u8 {
+ self.size
+ }
+
+ fn is_symmetric(&self) -> bool {
+ self.symmetric
+ }
+
+ fn create_build_cost(&self) -> Option<Storage> {
+ if !self.build_cost.is_empty() {
+ let mut storage = Storage::new();
+ for (ty, cnt) in self.build_cost {
+ storage.add(*ty, *cnt, u32::MAX);
+ }
+ Some(storage)
+ } else {
+ None
+ }
+ }
+
+ fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ Ok(DynData::Empty)
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> {
+ match data {
+ DynData::Empty => Ok(Some(Self::create_state(true))),
+ DynData::Boolean(enabled) => Ok(Some(Self::create_state(enabled))),
+ _ => Err(DeserializeError::InvalidType {
+ have: data.get_type(),
+ expect: DynType::Boolean,
+ }),
+ }
+ }
+
+ fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> {
+ Box::new(Self::get_state(state).clone())
+ }
+
+ fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool) {}
+
+ fn rotate_state(&self, _: &mut dyn Any, _: bool) {}
+
+ fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> {
+ Ok(DynData::Boolean(*Self::get_state(state)))
+ }
}
-pub struct ProcessorLogic
-{
- size: u8,
- symmetric: bool,
- build_cost: BuildCost,
+pub struct ProcessorLogic {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
}
-impl ProcessorLogic
-{
- pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self
- {
- if size == 0
- {
- panic!("invalid size");
- }
- Self{size, symmetric, build_cost}
- }
-
- state_impl!(pub ProcessorState);
+impl ProcessorLogic {
+ pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self {
+ if size == 0 {
+ panic!("invalid size");
+ }
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ }
+ }
+
+ state_impl!(pub ProcessorState);
}
-impl BlockLogic for ProcessorLogic
-{
- fn get_size(&self) -> u8
- {
- self.size
- }
-
- fn is_symmetric(&self) -> bool
- {
- self.symmetric
- }
-
- fn create_build_cost(&self) -> Option<Storage>
- {
- if !self.build_cost.is_empty()
- {
- let mut storage = Storage::new();
- for (ty, cnt) in self.build_cost
- {
- storage.add(*ty, *cnt, u32::MAX);
- }
- Some(storage)
- }
- else {None}
- }
-
- fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError>
- {
- Ok(DynData::Empty)
- }
-
- fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError>
- {
- match data
- {
- DynData::Empty => Ok(Some(Self::create_state(ProcessorState::new()))),
- DynData::ByteArray(arr) =>
- {
- let mut input = arr.as_ref();
- let mut dec = Decompress::new(true);
- let mut raw = Vec::<u8>::new();
- raw.reserve(1024);
- loop
- {
- let t_in = dec.total_in();
- let t_out = dec.total_out();
- let res = ProcessorDeserializeError::forward(dec.decompress_vec(input, &mut raw, FlushDecompress::Finish))?;
- if dec.total_in() > t_in
- {
- // we have to advance input every time, decompress_vec only knows the output position
- input = &input[(dec.total_in() - t_in) as usize..];
- }
- match res
- {
- // there's no more input (and the flush mode says so), we need to reserve additional space
- Status::Ok | Status::BufError => (),
- // input was already at the end, so this is referring to the output
- Status::StreamEnd => break,
- }
- if dec.total_in() == t_in && dec.total_out() == t_out
- {
- // protect against looping forever
- return Err(DeserializeError::Custom(Box::new(ProcessorDeserializeError::DecompressStall)));
- }
- raw.reserve(1024);
- }
- let mut buff = DataRead::new(&raw);
- let ver = ProcessorDeserializeError::forward(buff.read_u8())?;
- if ver != 1
- {
- return Err(DeserializeError::Custom(Box::new(ProcessorDeserializeError::Version(ver))));
- }
-
- let code_len = ProcessorDeserializeError::forward(buff.read_i32())?;
- if code_len < 0 || code_len > 500 * 1024
- {
- return Err(DeserializeError::Custom(Box::new(ProcessorDeserializeError::CodeLength(code_len))));
- }
- let mut code = Vec::<u8>::new();
- code.resize(code_len as usize, 0);
- ProcessorDeserializeError::forward(buff.read_bytes(&mut code))?;
- let code = ProcessorDeserializeError::forward(String::from_utf8(code))?;
- let link_cnt = ProcessorDeserializeError::forward(buff.read_i32())?;
- if link_cnt < 0
- {
- return Err(DeserializeError::Custom(Box::new(ProcessorDeserializeError::LinkCount(link_cnt))));
- }
- let mut links = Vec::<ProcessorLink>::new();
- links.reserve(link_cnt as usize);
- for _ in 0..link_cnt
- {
- let name = ProcessorDeserializeError::forward(buff.read_utf())?;
- let x = ProcessorDeserializeError::forward(buff.read_i16())?;
- let y = ProcessorDeserializeError::forward(buff.read_i16())?;
- links.push(ProcessorLink{name: String::from(name), x, y});
- }
- Ok(Some(Self::create_state(ProcessorState{code, links})))
- },
- _ => Err(DeserializeError::InvalidType{have: data.get_type(), expect: DynType::Boolean}),
- }
- }
-
- fn clone_state(&self, state: &dyn Any) -> Box<dyn Any>
- {
- Box::new(Self::get_state(state).clone())
- }
-
- fn mirror_state(&self, state: &mut dyn Any, horizontally: bool, vertically: bool)
- {
- for link in Self::get_state_mut(state).links.iter_mut()
- {
- if horizontally {link.x = -link.x;}
- if vertically {link.y = -link.y;}
- }
- }
-
- fn rotate_state(&self, state: &mut dyn Any, clockwise: bool)
- {
- for link in Self::get_state_mut(state).links.iter_mut()
- {
- let (cdx, cdy) = link.get_pos();
- link.x = if clockwise {cdy} else {-cdy};
- link.y = if clockwise {-cdx} else {cdx};
- }
- }
-
- fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError>
- {
- let state = Self::get_state(state);
- let mut rbuff = DataWrite::new();
- ProcessorSerializeError::forward(rbuff.write_u8(1))?;
- assert!(state.code.len() < 500 * 1024);
- ProcessorSerializeError::forward(rbuff.write_i32(state.code.len() as i32))?;
- ProcessorSerializeError::forward(rbuff.write_bytes(state.code.as_bytes()))?;
- assert!(state.links.len() < i32::MAX as usize);
- ProcessorSerializeError::forward(rbuff.write_i32(state.links.len() as i32))?;
- for link in state.links.iter()
- {
- ProcessorSerializeError::forward(rbuff.write_utf(&link.name))?;
- ProcessorSerializeError::forward(rbuff.write_i16(link.x))?;
- ProcessorSerializeError::forward(rbuff.write_i16(link.y))?;
- }
- let mut input = rbuff.get_written();
- let mut comp = Compress::new(Compression::default(), true);
- let mut dst = Vec::<u8>::new();
- dst.reserve(1024);
- loop
- {
- let t_in = comp.total_in();
- let t_out = comp.total_out();
- let res = ProcessorSerializeError::forward(comp.compress_vec(input, &mut dst, FlushCompress::Finish))?;
- if comp.total_in() > t_in
- {
- // we have to advance input every time, compress_vec only knows the output position
- input = &input[(comp.total_in() - t_in) as usize..];
- }
- match res
- {
- // there's no more input (and the flush mode says so), we need to reserve additional space
- Status::Ok | Status::BufError => (),
- // input was already at the end, so this is referring to the output
- Status::StreamEnd => break,
- }
- if comp.total_in() == t_in && comp.total_out() == t_out
- {
- // protect against looping forever
- return Err(SerializeError::Custom(Box::new(ProcessorSerializeError::CompressStall)));
- }
- dst.reserve(1024);
- }
- Ok(DynData::ByteArray(dst))
- }
+impl BlockLogic for ProcessorLogic {
+ fn get_size(&self) -> u8 {
+ self.size
+ }
+
+ fn is_symmetric(&self) -> bool {
+ self.symmetric
+ }
+
+ fn create_build_cost(&self) -> Option<Storage> {
+ if !self.build_cost.is_empty() {
+ let mut storage = Storage::new();
+ for (ty, cnt) in self.build_cost {
+ storage.add(*ty, *cnt, u32::MAX);
+ }
+ Some(storage)
+ } else {
+ None
+ }
+ }
+
+ fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ Ok(DynData::Empty)
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> {
+ match data {
+ DynData::Empty => Ok(Some(Self::create_state(ProcessorState::new()))),
+ DynData::ByteArray(arr) => {
+ let mut input = arr.as_ref();
+ let mut dec = Decompress::new(true);
+ let mut raw = Vec::<u8>::new();
+ raw.reserve(1024);
+ loop {
+ let t_in = dec.total_in();
+ let t_out = dec.total_out();
+ let res = ProcessorDeserializeError::forward(dec.decompress_vec(
+ input,
+ &mut raw,
+ FlushDecompress::Finish,
+ ))?;
+ if dec.total_in() > t_in {
+ // we have to advance input every time, decompress_vec only knows the output position
+ input = &input[(dec.total_in() - t_in) as usize..];
+ }
+ match res {
+ // there's no more input (and the flush mode says so), we need to reserve additional space
+ Status::Ok | Status::BufError => (),
+ // input was already at the end, so this is referring to the output
+ Status::StreamEnd => break,
+ }
+ if dec.total_in() == t_in && dec.total_out() == t_out {
+ // protect against looping forever
+ return Err(DeserializeError::Custom(Box::new(
+ ProcessorDeserializeError::DecompressStall,
+ )));
+ }
+ raw.reserve(1024);
+ }
+ let mut buff = DataRead::new(&raw);
+ let ver = ProcessorDeserializeError::forward(buff.read_u8())?;
+ if ver != 1 {
+ return Err(DeserializeError::Custom(Box::new(
+ ProcessorDeserializeError::Version(ver),
+ )));
+ }
+
+ let code_len = ProcessorDeserializeError::forward(buff.read_i32())?;
+ if code_len < 0 || code_len > 500 * 1024 {
+ return Err(DeserializeError::Custom(Box::new(
+ ProcessorDeserializeError::CodeLength(code_len),
+ )));
+ }
+ let mut code = Vec::<u8>::new();
+ code.resize(code_len as usize, 0);
+ ProcessorDeserializeError::forward(buff.read_bytes(&mut code))?;
+ let code = ProcessorDeserializeError::forward(String::from_utf8(code))?;
+ let link_cnt = ProcessorDeserializeError::forward(buff.read_i32())?;
+ if link_cnt < 0 {
+ return Err(DeserializeError::Custom(Box::new(
+ ProcessorDeserializeError::LinkCount(link_cnt),
+ )));
+ }
+ let mut links = Vec::<ProcessorLink>::new();
+ links.reserve(link_cnt as usize);
+ for _ in 0..link_cnt {
+ let name = ProcessorDeserializeError::forward(buff.read_utf())?;
+ let x = ProcessorDeserializeError::forward(buff.read_i16())?;
+ let y = ProcessorDeserializeError::forward(buff.read_i16())?;
+ links.push(ProcessorLink {
+ name: String::from(name),
+ x,
+ y,
+ });
+ }
+ Ok(Some(Self::create_state(ProcessorState { code, links })))
+ }
+ _ => Err(DeserializeError::InvalidType {
+ have: data.get_type(),
+ expect: DynType::Boolean,
+ }),
+ }
+ }
+
+ fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> {
+ Box::new(Self::get_state(state).clone())
+ }
+
+ fn mirror_state(&self, state: &mut dyn Any, horizontally: bool, vertically: bool) {
+ for link in Self::get_state_mut(state).links.iter_mut() {
+ if horizontally {
+ link.x = -link.x;
+ }
+ if vertically {
+ link.y = -link.y;
+ }
+ }
+ }
+
+ fn rotate_state(&self, state: &mut dyn Any, clockwise: bool) {
+ for link in Self::get_state_mut(state).links.iter_mut() {
+ let (cdx, cdy) = link.get_pos();
+ link.x = if clockwise { cdy } else { -cdy };
+ link.y = if clockwise { -cdx } else { cdx };
+ }
+ }
+
+ fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> {
+ let state = Self::get_state(state);
+ let mut rbuff = DataWrite::new();
+ ProcessorSerializeError::forward(rbuff.write_u8(1))?;
+ assert!(state.code.len() < 500 * 1024);
+ ProcessorSerializeError::forward(rbuff.write_i32(state.code.len() as i32))?;
+ ProcessorSerializeError::forward(rbuff.write_bytes(state.code.as_bytes()))?;
+ assert!(state.links.len() < i32::MAX as usize);
+ ProcessorSerializeError::forward(rbuff.write_i32(state.links.len() as i32))?;
+ for link in state.links.iter() {
+ ProcessorSerializeError::forward(rbuff.write_utf(&link.name))?;
+ ProcessorSerializeError::forward(rbuff.write_i16(link.x))?;
+ ProcessorSerializeError::forward(rbuff.write_i16(link.y))?;
+ }
+ let mut input = rbuff.get_written();
+ let mut comp = Compress::new(Compression::default(), true);
+ let mut dst = Vec::<u8>::new();
+ dst.reserve(1024);
+ loop {
+ let t_in = comp.total_in();
+ let t_out = comp.total_out();
+ let res = ProcessorSerializeError::forward(comp.compress_vec(
+ input,
+ &mut dst,
+ FlushCompress::Finish,
+ ))?;
+ if comp.total_in() > t_in {
+ // we have to advance input every time, compress_vec only knows the output position
+ input = &input[(comp.total_in() - t_in) as usize..];
+ }
+ match res {
+ // there's no more input (and the flush mode says so), we need to reserve additional space
+ Status::Ok | Status::BufError => (),
+ // input was already at the end, so this is referring to the output
+ Status::StreamEnd => break,
+ }
+ if comp.total_in() == t_in && comp.total_out() == t_out {
+ // protect against looping forever
+ return Err(SerializeError::Custom(Box::new(
+ ProcessorSerializeError::CompressStall,
+ )));
+ }
+ dst.reserve(1024);
+ }
+ Ok(DynData::ByteArray(dst))
+ }
}
#[derive(Debug)]
-pub enum ProcessorDeserializeError
-{
- Read(data::ReadError),
- Decompress(DecompressError),
- DecompressStall,
- FromUtf8(FromUtf8Error),
- Version(u8),
- CodeLength(i32),
- LinkCount(i32),
+pub enum ProcessorDeserializeError {
+ Read(data::ReadError),
+ Decompress(DecompressError),
+ DecompressStall,
+ FromUtf8(FromUtf8Error),
+ Version(u8),
+ CodeLength(i32),
+ LinkCount(i32),
}
-impl ProcessorDeserializeError
-{
- pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, DeserializeError>
- {
- match result
- {
- Ok(v) => Ok(v),
- Err(e) => Err(DeserializeError::Custom(Box::new(e.into()))),
- }
- }
+impl ProcessorDeserializeError {
+ pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, DeserializeError> {
+ match result {
+ Ok(v) => Ok(v),
+ Err(e) => Err(DeserializeError::Custom(Box::new(e.into()))),
+ }
+ }
}
-impl From<data::ReadError> for ProcessorDeserializeError
-{
- fn from(value: data::ReadError) -> Self
- {
- Self::Read(value)
- }
+impl From<data::ReadError> for ProcessorDeserializeError {
+ fn from(value: data::ReadError) -> Self {
+ Self::Read(value)
+ }
}
-impl From<DecompressError> for ProcessorDeserializeError
-{
- fn from(value: DecompressError) -> Self
- {
- Self::Decompress(value)
- }
+impl From<DecompressError> for ProcessorDeserializeError {
+ fn from(value: DecompressError) -> Self {
+ Self::Decompress(value)
+ }
}
-impl From<FromUtf8Error> for ProcessorDeserializeError
-{
- fn from(value: FromUtf8Error) -> Self
- {
- Self::FromUtf8(value)
- }
+impl From<FromUtf8Error> for ProcessorDeserializeError {
+ fn from(value: FromUtf8Error) -> Self {
+ Self::FromUtf8(value)
+ }
}
-impl fmt::Display for ProcessorDeserializeError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Read(..) => f.write_str("failed to read state data"),
- Self::Decompress(..) => f.write_str("zlib decompression failed"),
- Self::DecompressStall => f.write_str("decompressor stalled before completion"),
- Self::FromUtf8(..) => f.write_str("malformed utf-8 in processor code"),
- Self::Version(ver) => write!(f, "unsupported version ({ver})"),
- Self::CodeLength(len) => write!(f, "invalid code length ({len})"),
- Self::LinkCount(cnt) => write!(f, "invalid link count ({cnt})"),
- }
- }
+impl fmt::Display for ProcessorDeserializeError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Read(..) => f.write_str("failed to read state data"),
+ Self::Decompress(..) => f.write_str("zlib decompression failed"),
+ Self::DecompressStall => f.write_str("decompressor stalled before completion"),
+ Self::FromUtf8(..) => f.write_str("malformed utf-8 in processor code"),
+ Self::Version(ver) => write!(f, "unsupported version ({ver})"),
+ Self::CodeLength(len) => write!(f, "invalid code length ({len})"),
+ Self::LinkCount(cnt) => write!(f, "invalid link count ({cnt})"),
+ }
+ }
}
-impl Error for ProcessorDeserializeError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::Decompress(e) => Some(e),
- Self::FromUtf8(e) => Some(e),
- _ => None,
- }
- }
+impl Error for ProcessorDeserializeError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Decompress(e) => Some(e),
+ Self::FromUtf8(e) => Some(e),
+ _ => None,
+ }
+ }
}
#[derive(Debug)]
-pub enum ProcessorSerializeError
-{
- Write(data::WriteError),
- Compress(CompressError),
- CompressEof(usize),
- CompressStall,
+pub enum ProcessorSerializeError {
+ Write(data::WriteError),
+ Compress(CompressError),
+ CompressEof(usize),
+ CompressStall,
}
-impl ProcessorSerializeError
-{
- pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, SerializeError>
- {
- match result
- {
- Ok(v) => Ok(v),
- Err(e) => Err(SerializeError::Custom(Box::new(e.into()))),
- }
- }
+impl ProcessorSerializeError {
+ pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, SerializeError> {
+ match result {
+ Ok(v) => Ok(v),
+ Err(e) => Err(SerializeError::Custom(Box::new(e.into()))),
+ }
+ }
}
-impl From<data::WriteError> for ProcessorSerializeError
-{
- fn from(value: data::WriteError) -> Self
- {
- Self::Write(value)
- }
+impl From<data::WriteError> for ProcessorSerializeError {
+ fn from(value: data::WriteError) -> Self {
+ Self::Write(value)
+ }
}
-impl From<CompressError> for ProcessorSerializeError
-{
- fn from(value: CompressError) -> Self
- {
- Self::Compress(value)
- }
+impl From<CompressError> for ProcessorSerializeError {
+ fn from(value: CompressError) -> Self {
+ Self::Compress(value)
+ }
}
-impl fmt::Display for ProcessorSerializeError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Write(..) => f.write_str("failed to write state data"),
- Self::Compress(..) => f.write_str("zlib compression failed"),
- Self::CompressEof(remain) => write!(f, "compression overflow with {remain} bytes of input remaining"),
- Self::CompressStall => f.write_str("compressor stalled before completion"),
- }
- }
+impl fmt::Display for ProcessorSerializeError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Write(..) => f.write_str("failed to write state data"),
+ Self::Compress(..) => f.write_str("zlib compression failed"),
+ Self::CompressEof(remain) => write!(
+ f,
+ "compression overflow with {remain} bytes of input remaining"
+ ),
+ Self::CompressStall => f.write_str("compressor stalled before completion"),
+ }
+ }
}
-impl Error for ProcessorSerializeError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::Compress(e) => Some(e),
- _ => None,
- }
- }
+impl Error for ProcessorSerializeError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Compress(e) => Some(e),
+ _ => None,
+ }
+ }
}
#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct ProcessorLink
-{
- name: String,
- x: i16,
- y: i16,
+pub struct ProcessorLink {
+ name: String,
+ x: i16,
+ y: i16,
}
-impl ProcessorLink
-{
- pub fn new(name: Cow<'_, str>, x: i16, y: i16) -> Self
- {
- if name.len() > u16::MAX as usize
- {
- panic!("name too long ({})", name.len());
- }
- Self{name: name.into_owned(), x, y}
- }
-
- pub fn get_name(&self) -> &str
- {
- &self.name
- }
-
- pub fn get_pos(&self) -> (i16, i16)
- {
- (self.x, self.y)
- }
+impl ProcessorLink {
+ pub fn new(name: Cow<'_, str>, x: i16, y: i16) -> Self {
+ if name.len() > u16::MAX as usize {
+ panic!("name too long ({})", name.len());
+ }
+ Self {
+ name: name.into_owned(),
+ x,
+ y,
+ }
+ }
+
+ pub fn get_name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn get_pos(&self) -> (i16, i16) {
+ (self.x, self.y)
+ }
}
#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct ProcessorState
-{
- code: String,
- links: Vec<ProcessorLink>
+pub struct ProcessorState {
+ code: String,
+ links: Vec<ProcessorLink>,
}
-impl ProcessorState
-{
- pub fn new() -> Self
- {
- Self{code: String::new(), links: Vec::new()}
- }
-
- pub fn get_code(&self) -> &str
- {
- &self.code
- }
-
- pub fn set_code(&mut self, code: Cow<'_, str>) -> Result<(), CodeError>
- {
- let as_str = &code as &str;
- if as_str.len() > 500 * 1024
- {
- return Err(CodeError::TooLong(as_str.len()));
- }
- match code
- {
- Cow::Borrowed(s) =>
- {
- self.code.clear();
- self.code.push_str(s);
- },
- Cow::Owned(s) => self.code = s,
- }
- Ok(())
- }
-
- pub fn get_links(&self) -> &[ProcessorLink]
- {
- &self.links
- }
-
- pub fn create_link(&mut self, mut name: String, x: i16, y: i16) -> Result<&ProcessorLink, CreateError>
- {
- if name.len() > u16::MAX as usize
- {
- return Err(CreateError::NameLength(name.len()))
- }
- for curr in self.links.iter()
- {
- if &name == &curr.name
- {
- return Err(CreateError::DuplicateName(name));
- }
- if x == curr.x && y == curr.y
- {
- name.clear();
- name.push_str(&curr.name);
- return Err(CreateError::DuplicatePos{name, x, y});
- }
- }
- let idx = self.links.len();
- self.links.push(ProcessorLink{name, x, y});
- Ok(&self.links[idx])
- }
-
- pub fn add_link(&mut self, link: ProcessorLink) -> Result<&ProcessorLink, CreateError>
- {
- self.create_link(link.name, link.x, link.y)
- }
-
- pub fn remove_link(&mut self, idx: usize) -> Option<ProcessorLink>
- {
- if idx < self.links.len()
- {
- Some(self.links.remove(idx))
- }
- else {None}
- }
+impl ProcessorState {
+ pub fn new() -> Self {
+ Self {
+ code: String::new(),
+ links: Vec::new(),
+ }
+ }
+
+ pub fn get_code(&self) -> &str {
+ &self.code
+ }
+
+ pub fn set_code(&mut self, code: Cow<'_, str>) -> Result<(), CodeError> {
+ let as_str = &code as &str;
+ if as_str.len() > 500 * 1024 {
+ return Err(CodeError::TooLong(as_str.len()));
+ }
+ match code {
+ Cow::Borrowed(s) => {
+ self.code.clear();
+ self.code.push_str(s);
+ }
+ Cow::Owned(s) => self.code = s,
+ }
+ Ok(())
+ }
+
+ pub fn get_links(&self) -> &[ProcessorLink] {
+ &self.links
+ }
+
+ pub fn create_link(
+ &mut self,
+ mut name: String,
+ x: i16,
+ y: i16,
+ ) -> Result<&ProcessorLink, CreateError> {
+ if name.len() > u16::MAX as usize {
+ return Err(CreateError::NameLength(name.len()));
+ }
+ for curr in self.links.iter() {
+ if &name == &curr.name {
+ return Err(CreateError::DuplicateName(name));
+ }
+ if x == curr.x && y == curr.y {
+ name.clear();
+ name.push_str(&curr.name);
+ return Err(CreateError::DuplicatePos { name, x, y });
+ }
+ }
+ let idx = self.links.len();
+ self.links.push(ProcessorLink { name, x, y });
+ Ok(&self.links[idx])
+ }
+
+ pub fn add_link(&mut self, link: ProcessorLink) -> Result<&ProcessorLink, CreateError> {
+ self.create_link(link.name, link.x, link.y)
+ }
+
+ pub fn remove_link(&mut self, idx: usize) -> Option<ProcessorLink> {
+ if idx < self.links.len() {
+ Some(self.links.remove(idx))
+ } else {
+ None
+ }
+ }
}
#[derive(Clone, Debug, Eq, PartialEq)]
-pub enum CodeError
-{
- TooLong(usize),
+pub enum CodeError {
+ TooLong(usize),
}
-impl fmt::Display for CodeError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::TooLong(len) => write!(f, "code too long ({len} bytes)"),
- }
- }
+impl fmt::Display for CodeError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::TooLong(len) => write!(f, "code too long ({len} bytes)"),
+ }
+ }
}
impl Error for CodeError {}
#[derive(Clone, Debug, Eq, PartialEq)]
-pub enum CreateError
-{
- NameLength(usize),
- DuplicateName(String),
- DuplicatePos{name: String, x: i16, y: i16},
+pub enum CreateError {
+ NameLength(usize),
+ DuplicateName(String),
+ DuplicatePos { name: String, x: i16, y: i16 },
}
-impl fmt::Display for CreateError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::NameLength(len) => write!(f, "link name too long ({len} bytes)"),
- Self::DuplicateName(name) => write!(f, "there already is a link named {name}"),
- Self::DuplicatePos{name, x, y} => write!(f, "link {name} already points to {x} / {y}"),
- }
- }
+impl fmt::Display for CreateError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::NameLength(len) => write!(f, "link name too long ({len} bytes)"),
+ Self::DuplicateName(name) => write!(f, "there already is a link named {name}"),
+ Self::DuplicatePos { name, x, y } => {
+ write!(f, "link {name} already points to {x} / {y}")
+ }
+ }
+ }
}
impl Error for CreateError {}
diff --git a/src/block/mod.rs b/src/block/mod.rs
index 1ddd337..b3b7e11 100644
--- a/src/block/mod.rs
+++ b/src/block/mod.rs
@@ -4,8 +4,8 @@ use std::error::Error;
use std::fmt;
use crate::access::BoxAccess;
-use crate::data::GridPos;
use crate::data::dynamic::{DynData, DynType};
+use crate::data::GridPos;
use crate::item::storage::Storage as ItemStorage;
use crate::registry::RegistryEntry;
use crate::utils::OnceCell;
@@ -23,354 +23,353 @@ pub mod simple;
pub mod transport;
pub mod turret;
-pub trait BlockLogic
-{
- fn get_size(&self) -> u8;
-
- fn is_symmetric(&self) -> bool;
-
- fn create_build_cost(&self) -> Option<ItemStorage>;
-
- fn data_from_i32(&self, config: i32, pos: GridPos) -> Result<DynData, DataConvertError>;
-
- fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError>;
-
- fn clone_state(&self, state: &dyn Any) -> Box<dyn Any>;
-
- fn mirror_state(&self, state: &mut dyn Any, horizontally: bool, vertically: bool);
-
- fn rotate_state(&self, state: &mut dyn Any, clockwise: bool);
-
- fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError>;
+pub trait BlockLogic {
+ fn get_size(&self) -> u8;
+
+ fn is_symmetric(&self) -> bool;
+
+ fn create_build_cost(&self) -> Option<ItemStorage>;
+
+ fn data_from_i32(&self, config: i32, pos: GridPos) -> Result<DynData, DataConvertError>;
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError>;
+
+ fn clone_state(&self, state: &dyn Any) -> Box<dyn Any>;
+
+ fn mirror_state(&self, state: &mut dyn Any, horizontally: bool, vertically: bool);
+
+ fn rotate_state(&self, state: &mut dyn Any, clockwise: bool);
+
+ fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError>;
}
#[derive(Debug)]
-pub enum DataConvertError
-{
- Custom(Box<dyn Error>),
+pub enum DataConvertError {
+ Custom(Box<dyn Error>),
}
-impl DataConvertError
-{
- pub fn forward<T, E: Error + 'static>(result: Result<T, E>) -> Result<T, Self>
- {
- match result
- {
- Ok(v) => Ok(v),
- Err(e) => Err(Self::Custom(Box::new(e))),
- }
- }
+impl DataConvertError {
+ pub fn forward<T, E: Error + 'static>(result: Result<T, E>) -> Result<T, Self> {
+ match result {
+ Ok(v) => Ok(v),
+ Err(e) => Err(Self::Custom(Box::new(e))),
+ }
+ }
}
-impl fmt::Display for DataConvertError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Custom(e) => e.fmt(f),
- }
- }
+impl fmt::Display for DataConvertError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Custom(e) => e.fmt(f),
+ }
+ }
}
-impl Error for DataConvertError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::Custom(e) => e.source(),
- }
- }
+impl Error for DataConvertError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Custom(e) => e.source(),
+ }
+ }
}
#[derive(Debug)]
-pub enum DeserializeError
-{
- InvalidType{have: DynType, expect: DynType},
- Custom(Box<dyn Error>),
+pub enum DeserializeError {
+ InvalidType { have: DynType, expect: DynType },
+ Custom(Box<dyn Error>),
}
-impl DeserializeError
-{
- pub fn forward<T, E: Error + 'static>(result: Result<T, E>) -> Result<T, Self>
- {
- match result
- {
- Ok(v) => Ok(v),
- Err(e) => Err(Self::Custom(Box::new(e))),
- }
- }
+impl DeserializeError {
+ pub fn forward<T, E: Error + 'static>(result: Result<T, E>) -> Result<T, Self> {
+ match result {
+ Ok(v) => Ok(v),
+ Err(e) => Err(Self::Custom(Box::new(e))),
+ }
+ }
}
-impl fmt::Display for DeserializeError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::InvalidType{have, expect} => write!(f, "expected type {expect:?} but got {have:?}"),
- Self::Custom(e) => e.fmt(f),
- }
- }
+impl fmt::Display for DeserializeError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::InvalidType { have, expect } => {
+ write!(f, "expected type {expect:?} but got {have:?}")
+ }
+ Self::Custom(e) => e.fmt(f),
+ }
+ }
}
-impl Error for DeserializeError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::Custom(e) => e.source(),
- _ => None,
- }
- }
+impl Error for DeserializeError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Custom(e) => e.source(),
+ _ => None,
+ }
+ }
}
#[derive(Debug)]
-pub enum SerializeError
-{
- Custom(Box<dyn Error>),
+pub enum SerializeError {
+ Custom(Box<dyn Error>),
}
-impl SerializeError
-{
- pub fn forward<T, E: Error + 'static>(result: Result<T, E>) -> Result<T, Self>
- {
- match result
- {
- Ok(v) => Ok(v),
- Err(e) => Err(Self::Custom(Box::new(e))),
- }
- }
+impl SerializeError {
+ pub fn forward<T, E: Error + 'static>(result: Result<T, E>) -> Result<T, Self> {
+ match result {
+ Ok(v) => Ok(v),
+ Err(e) => Err(Self::Custom(Box::new(e))),
+ }
+ }
}
-impl fmt::Display for SerializeError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Custom(e) => e.fmt(f),
- }
- }
+impl fmt::Display for SerializeError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Custom(e) => e.fmt(f),
+ }
+ }
}
-impl Error for SerializeError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::Custom(e) => e.source(),
- }
- }
+impl Error for SerializeError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Custom(e) => e.source(),
+ }
+ }
}
-pub struct Block
-{
- name: Cow<'static, str>,
- logic: BoxAccess<'static, dyn BlockLogic + Sync>,
- build_cost: OnceCell<Option<ItemStorage>>,
+pub struct Block {
+ name: Cow<'static, str>,
+ logic: BoxAccess<'static, dyn BlockLogic + Sync>,
+ build_cost: OnceCell<Option<ItemStorage>>,
}
-impl Block
-{
- pub const fn new(name: Cow<'static, str>, logic: BoxAccess<'static, dyn BlockLogic + Sync>) -> Self
- {
- Self{name, logic, build_cost: OnceCell::new()}
- }
-
- pub fn get_size(&self) -> u8
- {
- self.logic.get_size()
- }
-
- pub fn is_symmetric(&self) -> bool
- {
- self.logic.is_symmetric()
- }
-
- pub fn get_build_cost(&self) -> Option<&ItemStorage>
- {
- self.build_cost.get_or_init(|| self.logic.as_ref().create_build_cost()).as_ref()
- }
-
- pub fn data_from_i32(&self, config: i32, pos: GridPos) -> Result<DynData, DataConvertError>
- {
- self.logic.data_from_i32(config, pos)
- }
-
- pub fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError>
- {
- self.logic.deserialize_state(data)
- }
-
- pub fn clone_state(&self, state: &dyn Any) -> Box<dyn Any>
- {
- self.logic.clone_state(state)
- }
-
- pub fn mirror_state(&self, state: &mut dyn Any, horizontally: bool, vertically: bool)
- {
- self.logic.mirror_state(state, horizontally, vertically);
- }
-
- pub fn rotate_state(&self, state: &mut dyn Any, clockwise: bool)
- {
- self.logic.rotate_state(state, clockwise);
- }
-
- pub fn rotate_180(&mut self, state: &mut dyn Any)
- {
- self.logic.mirror_state(state, true, true);
- }
-
- pub fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError>
- {
- self.logic.serialize_state(state)
- }
+impl Block {
+ pub const fn new(
+ name: Cow<'static, str>,
+ logic: BoxAccess<'static, dyn BlockLogic + Sync>,
+ ) -> Self {
+ Self {
+ name,
+ logic,
+ build_cost: OnceCell::new(),
+ }
+ }
+
+ pub fn get_size(&self) -> u8 {
+ self.logic.get_size()
+ }
+
+ pub fn is_symmetric(&self) -> bool {
+ self.logic.is_symmetric()
+ }
+
+ pub fn get_build_cost(&self) -> Option<&ItemStorage> {
+ self.build_cost
+ .get_or_init(|| self.logic.as_ref().create_build_cost())
+ .as_ref()
+ }
+
+ pub fn data_from_i32(&self, config: i32, pos: GridPos) -> Result<DynData, DataConvertError> {
+ self.logic.data_from_i32(config, pos)
+ }
+
+ pub fn deserialize_state(
+ &self,
+ data: DynData,
+ ) -> Result<Option<Box<dyn Any>>, DeserializeError> {
+ self.logic.deserialize_state(data)
+ }
+
+ pub fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> {
+ self.logic.clone_state(state)
+ }
+
+ pub fn mirror_state(&self, state: &mut dyn Any, horizontally: bool, vertically: bool) {
+ self.logic.mirror_state(state, horizontally, vertically);
+ }
+
+ pub fn rotate_state(&self, state: &mut dyn Any, clockwise: bool) {
+ self.logic.rotate_state(state, clockwise);
+ }
+
+ pub fn rotate_180(&mut self, state: &mut dyn Any) {
+ self.logic.mirror_state(state, true, true);
+ }
+
+ pub fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> {
+ self.logic.serialize_state(state)
+ }
}
-impl fmt::Debug for Block
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- let name: &str = &self.name;
- write!(f, "Block {{ name: {:?} }}", name)
- }
+impl fmt::Debug for Block {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let name: &str = &self.name;
+ write!(f, "Block {{ name: {:?} }}", name)
+ }
}
-impl RegistryEntry for Block
-{
- fn get_name(&self) -> &str
- {
- &self.name
- }
+impl RegistryEntry for Block {
+ fn get_name(&self) -> &str {
+ &self.name
+ }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum Rotation
-{
- Right, Up, Left, Down
+pub enum Rotation {
+ Right,
+ Up,
+ Left,
+ Down,
}
-impl Rotation
-{
- pub fn mirrored(self, horizontally: bool, vertically: bool) -> Self
- {
- match self
- {
- Self::Right => if horizontally {Self::Left} else {Self::Right},
- Self::Up => if vertically {Self::Down} else {Self::Up},
- Self::Left => if horizontally {Self::Right} else {Self::Left},
- Self::Down => if vertically {Self::Up} else {Self::Down},
- }
- }
-
- pub fn mirror(&mut self, horizontally: bool, vertically: bool)
- {
- *self = self.mirrored(horizontally, vertically);
- }
-
- pub fn rotated(self, clockwise: bool) -> Self
- {
- match self
- {
- Self::Right => if clockwise {Self::Down} else {Self::Up},
- Self::Up => if clockwise {Self::Right} else {Self::Left},
- Self::Left => if clockwise {Self::Up} else {Self::Down},
- Self::Down => if clockwise {Self::Left} else {Self::Right},
- }
- }
-
- pub fn rotate(&mut self, clockwise: bool)
- {
- *self = self.rotated(clockwise);
- }
-
- pub fn rotated_180(self) -> Self
- {
- match self
- {
- Self::Right => Self::Left,
- Self::Up => Self::Down,
- Self::Left => Self::Right,
- Self::Down => Self::Up,
- }
- }
-
- pub fn rotate_180(&mut self)
- {
- *self = self.rotated_180();
- }
+impl Rotation {
+ pub fn mirrored(self, horizontally: bool, vertically: bool) -> Self {
+ match self {
+ Self::Right => {
+ if horizontally {
+ Self::Left
+ } else {
+ Self::Right
+ }
+ }
+ Self::Up => {
+ if vertically {
+ Self::Down
+ } else {
+ Self::Up
+ }
+ }
+ Self::Left => {
+ if horizontally {
+ Self::Right
+ } else {
+ Self::Left
+ }
+ }
+ Self::Down => {
+ if vertically {
+ Self::Up
+ } else {
+ Self::Down
+ }
+ }
+ }
+ }
+
+ pub fn mirror(&mut self, horizontally: bool, vertically: bool) {
+ *self = self.mirrored(horizontally, vertically);
+ }
+
+ pub fn rotated(self, clockwise: bool) -> Self {
+ match self {
+ Self::Right => {
+ if clockwise {
+ Self::Down
+ } else {
+ Self::Up
+ }
+ }
+ Self::Up => {
+ if clockwise {
+ Self::Right
+ } else {
+ Self::Left
+ }
+ }
+ Self::Left => {
+ if clockwise {
+ Self::Up
+ } else {
+ Self::Down
+ }
+ }
+ Self::Down => {
+ if clockwise {
+ Self::Left
+ } else {
+ Self::Right
+ }
+ }
+ }
+ }
+
+ pub fn rotate(&mut self, clockwise: bool) {
+ *self = self.rotated(clockwise);
+ }
+
+ pub fn rotated_180(self) -> Self {
+ match self {
+ Self::Right => Self::Left,
+ Self::Up => Self::Down,
+ Self::Left => Self::Right,
+ Self::Down => Self::Up,
+ }
+ }
+
+ pub fn rotate_180(&mut self) {
+ *self = self.rotated_180();
+ }
}
-impl From<u8> for Rotation
-{
- fn from(val: u8) -> Self
- {
- match val & 3
- {
- 0 => Self::Right,
- 1 => Self::Up,
- 2 => Self::Left,
- _ => Self::Down,
- }
- }
+impl From<u8> for Rotation {
+ fn from(val: u8) -> Self {
+ match val & 3 {
+ 0 => Self::Right,
+ 1 => Self::Up,
+ 2 => Self::Left,
+ _ => Self::Down,
+ }
+ }
}
-impl From<Rotation> for u8
-{
- fn from(rot: Rotation) -> Self
- {
- match rot
- {
- Rotation::Right => 0,
- Rotation::Up => 1,
- Rotation::Left => 2,
- Rotation::Down => 3,
- }
- }
+impl From<Rotation> for u8 {
+ fn from(rot: Rotation) -> Self {
+ match rot {
+ Rotation::Right => 0,
+ Rotation::Up => 1,
+ Rotation::Left => 2,
+ Rotation::Down => 3,
+ }
+ }
}
pub type BlockRegistry<'l> = crate::registry::Registry<'l, Block>;
pub type RegisterError<'l> = crate::registry::RegisterError<'l, Block>;
-macro_rules!make_register
+macro_rules! make_register
{
- ($($field:ident: $name:literal => $logic:expr;)+) =>
+ ($($field:literal => $logic:expr;)+) =>
{
+ paste::paste! {
$(
- pub static $field: $crate::block::Block = $crate::block::Block::new(
- std::borrow::Cow::Borrowed($name), $crate::access::Access::Borrowed(&$logic));
+ pub static [<$field:snake:upper>]: $crate::block::Block = $crate::block::Block::new(
+ std::borrow::Cow::Borrowed($field), $crate::access::Access::Borrowed(&$logic));
)+
-
- pub fn register<'l>(reg: &mut $crate::block::BlockRegistry<'l>)
- {
- $(assert!(reg.register(&$field).is_ok(), "duplicate block {:?}", $name);)+
+
+ pub fn register<'l>(reg: &mut $crate::block::BlockRegistry<'l>) {
+ $(assert!(reg.register(&[<$field:snake:upper>]).is_ok(), "duplicate block {:?}", $field);)+
}
+ }
};
}
pub(crate) use make_register;
-pub fn build_registry() -> BlockRegistry<'static>
-{
- let mut reg = BlockRegistry::new();
- register(&mut reg);
- reg
+pub fn build_registry() -> BlockRegistry<'static> {
+ let mut reg = BlockRegistry::new();
+ register(&mut reg);
+ reg
}
-pub fn register<'l>(reg: &mut BlockRegistry<'l>)
-{
- turret::register(reg);
- extraction::register(reg);
- transport::register(reg);
- fluid::register(reg);
- power::register(reg);
- defense::register(reg);
- factory::register(reg);
- payload::register(reg);
- base::register(reg);
- logic::register(reg);
+pub fn register<'l>(reg: &mut BlockRegistry<'l>) {
+ turret::register(reg);
+ extraction::register(reg);
+ transport::register(reg);
+ fluid::register(reg);
+ power::register(reg);
+ defense::register(reg);
+ factory::register(reg);
+ payload::register(reg);
+ base::register(reg);
+ logic::register(reg);
}
diff --git a/src/block/payload.rs b/src/block/payload.rs
index bf6bc76..9803d11 100644
--- a/src/block/payload.rs
+++ b/src/block/payload.rs
@@ -2,11 +2,13 @@ use std::any::Any;
use std::error::Error;
use std::fmt;
-use crate::block::{self, BlockLogic, DataConvertError, DeserializeError, make_register, SerializeError};
-use crate::block::simple::{BuildCost, cost, SimpleBlock, state_impl};
+use crate::block::simple::{cost, state_impl, BuildCost, SimpleBlock};
+use crate::block::{
+ self, make_register, BlockLogic, DataConvertError, DeserializeError, SerializeError,
+};
use crate::content;
-use crate::data::GridPos;
use crate::data::dynamic::{DynData, DynType};
+use crate::data::GridPos;
use crate::item::storage::Storage;
use crate::unit;
@@ -14,158 +16,147 @@ const GROUND_UNITS: &[unit::Type] = &[unit::Type::Dagger, unit::Type::Crawler, u
const AIR_UNITS: &[unit::Type] = &[unit::Type::Flare, unit::Type::Mono];
const NAVAL_UNITS: &[unit::Type] = &[unit::Type::Risso, unit::Type::Retusa];
-make_register!
-(
- GROUND_FACTORY: "ground-factory" => AssemblerBlock::new(3, false, cost!(Copper: 50, Lead: 120, Silicon: 80), GROUND_UNITS);
- AIR_FACTORY: "air-factory" => AssemblerBlock::new(3, false, cost!(Copper: 60, Lead: 70), AIR_UNITS);
- NAVAL_FACTORY: "naval-factory" => AssemblerBlock::new(3, false, cost!(Copper: 150, Lead: 130, Metaglass: 120), NAVAL_UNITS);
- ADDITIVE_RECONSTRUCTOR: "additive-reconstructor" => SimpleBlock::new(3, false, cost!(Copper: 200, Lead: 120, Silicon: 90));
- MULTIPLICATIVE_RECONSTRUCTOR: "multiplicative-reconstructor" => SimpleBlock::new(5, false, cost!(Lead: 650, Titanium: 350, Thorium: 650, Silicon: 450));
- EXPONENTIAL_RECONSTRUCTOR: "exponential-reconstructor" => SimpleBlock::new(7, false,
- cost!(Lead: 2000, Titanium: 2000, Thorium: 750, Silicon: 1000, Plastanium: 450, PhaseFabric: 600));
- TETRATIVE_RECONSTRUCTOR: "tetrative-reconstructor" => SimpleBlock::new(9, false,
- cost!(Lead: 4000, Thorium: 1000, Silicon: 3000, Plastanium: 600, PhaseFabric: 600, SurgeAlloy: 800));
- REPAIR_POINT: "repair-point" => SimpleBlock::new(1, true, cost!(Copper: 30, Lead: 30, Silicon: 20));
- REPAIR_TURRET: "repair-turret" => SimpleBlock::new(2, true, cost!(Thorium: 80, Silicon: 90, Plastanium: 60));
- PAYLOAD_CONVEYOR: "payload-conveyor" => SimpleBlock::new(3, false, cost!(Copper: 10, Graphite: 10));
- PAYLOAD_ROUTER: "payload-router" => SimpleBlock::new(3, false, cost!(Copper: 10, Graphite: 15));
- // sandbox only
- PAYLOAD_SOURCE: "payload-source" => PayloadBlock::new(5, false, &[]);
- PAYLOAD_VOID: "payload-void" => SimpleBlock::new(5, true, &[]);
-);
-
-pub struct AssemblerBlock
-{
- size: u8,
- symmetric: bool,
- build_cost: BuildCost,
- valid: &'static [unit::Type],
+make_register! {
+ "ground-factory" => AssemblerBlock::new(3, false, cost!(Copper: 50, Lead: 120, Silicon: 80), GROUND_UNITS);
+ "air-factory" => AssemblerBlock::new(3, false, cost!(Copper: 60, Lead: 70), AIR_UNITS);
+ "naval-factory" => AssemblerBlock::new(3, false, cost!(Copper: 150, Lead: 130, Metaglass: 120), NAVAL_UNITS);
+ "additive-reconstructor" => SimpleBlock::new(3, false, cost!(Copper: 200, Lead: 120, Silicon: 90));
+ "multiplicative-reconstructor" => SimpleBlock::new(5, false, cost!(Lead: 650, Titanium: 350, Thorium: 650, Silicon: 450));
+ "exponential-reconstructor" => SimpleBlock::new(7, false,
+ cost!(Lead: 2000, Titanium: 2000, Thorium: 750, Silicon: 1000, Plastanium: 450, PhaseFabric: 600));
+ "tetrative-reconstructor" => SimpleBlock::new(9, false,
+ cost!(Lead: 4000, Thorium: 1000, Silicon: 3000, Plastanium: 600, PhaseFabric: 600, SurgeAlloy: 800));
+ "repair-point" => SimpleBlock::new(1, true, cost!(Copper: 30, Lead: 30, Silicon: 20));
+ "repair-turret" => SimpleBlock::new(2, true, cost!(Thorium: 80, Silicon: 90, Plastanium: 60));
+ "payload-conveyor" => SimpleBlock::new(3, false, cost!(Copper: 10, Graphite: 10));
+ "payload-router" => SimpleBlock::new(3, false, cost!(Copper: 10, Graphite: 15));
+ // sandbox only
+ "payload-source" => PayloadBlock::new(5, false, &[]);
+ "payload-void" => SimpleBlock::new(5, true, &[]);
+}
+
+pub struct AssemblerBlock {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
+ valid: &'static [unit::Type],
}
-impl AssemblerBlock
-{
- pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost, valid: &'static [unit::Type]) -> Self
- {
- if size == 0
- {
- panic!("invalid size");
- }
- if valid.is_empty()
- {
- panic!("no valid units");
- }
- if valid.len() > i32::MAX as usize
- {
- panic!("too many valid units");
- }
- Self{size, symmetric, build_cost, valid}
- }
-
- state_impl!(pub Option<unit::Type>);
+impl AssemblerBlock {
+ pub const fn new(
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
+ valid: &'static [unit::Type],
+ ) -> Self {
+ if size == 0 {
+ panic!("invalid size");
+ }
+ if valid.is_empty() {
+ panic!("no valid units");
+ }
+ if valid.len() > i32::MAX as usize {
+ panic!("too many valid units");
+ }
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ valid,
+ }
+ }
+
+ state_impl!(pub Option<unit::Type>);
}
-impl BlockLogic for AssemblerBlock
-{
- fn get_size(&self) -> u8
- {
- self.size
- }
-
- fn is_symmetric(&self) -> bool
- {
- self.symmetric
- }
-
- fn create_build_cost(&self) -> Option<Storage>
- {
- if !self.build_cost.is_empty()
- {
- let mut storage = Storage::new();
- for (ty, cnt) in self.build_cost
- {
- storage.add(*ty, *cnt, u32::MAX);
- }
- Some(storage)
- }
- else {None}
- }
-
- fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError>
- {
- Ok(DynData::Int(-1))
- }
-
- fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError>
- {
- match data
- {
- DynData::Empty => Ok(Some(Self::create_state(None))),
- DynData::Int(idx) =>
- {
- if idx == -1
- {
- Ok(Some(Self::create_state(None)))
- }
- else if idx >= 0 && idx < self.valid.len() as i32
- {
- Ok(Some(Self::create_state(Some(self.valid[idx as usize]))))
- }
- else
- {
- Err(DeserializeError::Custom(Box::new(AssemblerDeserializeError{idx, count: self.valid.len() as i32})))
- }
- },
- _ => Err(DeserializeError::InvalidType{have: data.get_type(), expect: DynType::Int}),
- }
- }
-
- fn clone_state(&self, state: &dyn Any) -> Box<dyn Any>
- {
- let state = Self::get_state(state);
- Box::new(Self::create_state(*state))
- }
-
- fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool)
- {
- }
-
- fn rotate_state(&self, _: &mut dyn Any, _: bool)
- {
- }
-
- fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError>
- {
- if let Some(state) = Self::get_state(state)
- {
- for (i, curr) in self.valid.iter().enumerate()
- {
- if curr == state
- {
- return Ok(DynData::Int(i as i32));
- }
- }
- Err(SerializeError::Custom(Box::new(AssemblerSerializeError(*state))))
- }
- else
- {
- Ok(DynData::Int(-1))
- }
- }
+impl BlockLogic for AssemblerBlock {
+ fn get_size(&self) -> u8 {
+ self.size
+ }
+
+ fn is_symmetric(&self) -> bool {
+ self.symmetric
+ }
+
+ fn create_build_cost(&self) -> Option<Storage> {
+ if !self.build_cost.is_empty() {
+ let mut storage = Storage::new();
+ for (ty, cnt) in self.build_cost {
+ storage.add(*ty, *cnt, u32::MAX);
+ }
+ Some(storage)
+ } else {
+ None
+ }
+ }
+
+ fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ Ok(DynData::Int(-1))
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> {
+ match data {
+ DynData::Empty => Ok(Some(Self::create_state(None))),
+ DynData::Int(idx) => {
+ if idx == -1 {
+ Ok(Some(Self::create_state(None)))
+ } else if idx >= 0 && idx < self.valid.len() as i32 {
+ Ok(Some(Self::create_state(Some(self.valid[idx as usize]))))
+ } else {
+ Err(DeserializeError::Custom(Box::new(
+ AssemblerDeserializeError {
+ idx,
+ count: self.valid.len() as i32,
+ },
+ )))
+ }
+ }
+ _ => Err(DeserializeError::InvalidType {
+ have: data.get_type(),
+ expect: DynType::Int,
+ }),
+ }
+ }
+
+ fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> {
+ let state = Self::get_state(state);
+ Box::new(Self::create_state(*state))
+ }
+
+ fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool) {}
+
+ fn rotate_state(&self, _: &mut dyn Any, _: bool) {}
+
+ fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> {
+ if let Some(state) = Self::get_state(state) {
+ for (i, curr) in self.valid.iter().enumerate() {
+ if curr == state {
+ return Ok(DynData::Int(i as i32));
+ }
+ }
+ Err(SerializeError::Custom(Box::new(AssemblerSerializeError(
+ *state,
+ ))))
+ } else {
+ Ok(DynData::Int(-1))
+ }
+ }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub struct AssemblerDeserializeError
-{
- pub idx: i32,
- pub count: i32,
+pub struct AssemblerDeserializeError {
+ pub idx: i32,
+ pub count: i32,
}
-impl fmt::Display for AssemblerDeserializeError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- write!(f, "invalid unit index ({}, #valid: {})", self.idx, self.count)
- }
+impl fmt::Display for AssemblerDeserializeError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "invalid unit index ({}, #valid: {})",
+ self.idx, self.count
+ )
+ }
}
impl Error for AssemblerDeserializeError {}
@@ -173,179 +164,155 @@ impl Error for AssemblerDeserializeError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AssemblerSerializeError(unit::Type);
-impl fmt::Display for AssemblerSerializeError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- write!(f, "invalid unit ({:?}) is not valid", self.0)
- }
+impl fmt::Display for AssemblerSerializeError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "invalid unit ({:?}) is not valid", self.0)
+ }
}
impl Error for AssemblerSerializeError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum Payload
-{
- Empty,
- Block(block::content::Type),
- Unit(unit::Type),
+pub enum Payload {
+ Empty,
+ Block(block::content::Type),
+ Unit(unit::Type),
}
-pub struct PayloadBlock
-{
- size: u8,
- symmetric: bool,
- build_cost: BuildCost,
+pub struct PayloadBlock {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
}
-impl PayloadBlock
-{
- pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self
- {
- if size == 0
- {
- panic!("invalid size");
- }
- Self{size, symmetric, build_cost}
- }
-
- state_impl!(pub Payload);
+impl PayloadBlock {
+ pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self {
+ if size == 0 {
+ panic!("invalid size");
+ }
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ }
+ }
+
+ state_impl!(pub Payload);
}
-impl BlockLogic for PayloadBlock
-{
- fn get_size(&self) -> u8
- {
- self.size
- }
-
- fn is_symmetric(&self) -> bool
- {
- self.symmetric
- }
-
- fn create_build_cost(&self) -> Option<Storage>
- {
- if !self.build_cost.is_empty()
- {
- let mut storage = Storage::new();
- for (ty, cnt) in self.build_cost
- {
- storage.add(*ty, *cnt, u32::MAX);
- }
- Some(storage)
- }
- else {None}
- }
-
- fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError>
- {
- Ok(DynData::Empty)
- }
-
- fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError>
- {
- match data
- {
- DynData::Empty => Ok(Some(Self::create_state(Payload::Empty))),
- DynData::Content(content::Type::Block, id) =>
- {
- let block = PayloadDeserializeError::forward(block::content::Type::try_from(id))?;
- Ok(Some(Self::create_state(Payload::Block(block))))
- },
- DynData::Content(content::Type::Unit, id) =>
- {
- let unit = PayloadDeserializeError::forward(unit::Type::try_from(id))?;
- Ok(Some(Self::create_state(Payload::Unit(unit))))
- },
- DynData::Content(have, ..) => Err(DeserializeError::Custom(Box::new(PayloadDeserializeError::ContentType(have)))),
- _ => Err(DeserializeError::InvalidType{have: data.get_type(), expect: DynType::Content}),
- }
- }
-
- fn clone_state(&self, state: &dyn Any) -> Box<dyn Any>
- {
- let state = Self::get_state(state);
- Box::new(Self::create_state(*state))
- }
-
- fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool)
- {
- }
-
- fn rotate_state(&self, _: &mut dyn Any, _: bool)
- {
- }
-
- fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError>
- {
- match Self::get_state(state)
- {
- Payload::Empty => Ok(DynData::Empty),
- Payload::Block(block) => Ok(DynData::Content(content::Type::Block, (*block).into())),
- Payload::Unit(unit) => Ok(DynData::Content(content::Type::Unit, (*unit).into())),
- }
- }
+impl BlockLogic for PayloadBlock {
+ fn get_size(&self) -> u8 {
+ self.size
+ }
+
+ fn is_symmetric(&self) -> bool {
+ self.symmetric
+ }
+
+ fn create_build_cost(&self) -> Option<Storage> {
+ if !self.build_cost.is_empty() {
+ let mut storage = Storage::new();
+ for (ty, cnt) in self.build_cost {
+ storage.add(*ty, *cnt, u32::MAX);
+ }
+ Some(storage)
+ } else {
+ None
+ }
+ }
+
+ fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ Ok(DynData::Empty)
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> {
+ match data {
+ DynData::Empty => Ok(Some(Self::create_state(Payload::Empty))),
+ DynData::Content(content::Type::Block, id) => {
+ let block = PayloadDeserializeError::forward(block::content::Type::try_from(id))?;
+ Ok(Some(Self::create_state(Payload::Block(block))))
+ }
+ DynData::Content(content::Type::Unit, id) => {
+ let unit = PayloadDeserializeError::forward(unit::Type::try_from(id))?;
+ Ok(Some(Self::create_state(Payload::Unit(unit))))
+ }
+ DynData::Content(have, ..) => Err(DeserializeError::Custom(Box::new(
+ PayloadDeserializeError::ContentType(have),
+ ))),
+ _ => Err(DeserializeError::InvalidType {
+ have: data.get_type(),
+ expect: DynType::Content,
+ }),
+ }
+ }
+
+ fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> {
+ let state = Self::get_state(state);
+ Box::new(Self::create_state(*state))
+ }
+
+ fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool) {}
+
+ fn rotate_state(&self, _: &mut dyn Any, _: bool) {}
+
+ fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> {
+ match Self::get_state(state) {
+ Payload::Empty => Ok(DynData::Empty),
+ Payload::Block(block) => Ok(DynData::Content(content::Type::Block, (*block).into())),
+ Payload::Unit(unit) => Ok(DynData::Content(content::Type::Unit, (*unit).into())),
+ }
+ }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum PayloadDeserializeError
-{
- ContentType(content::Type),
- BlockNotFound(block::content::TryFromU16Error),
- UnitNotFound(unit::TryFromU16Error),
+pub enum PayloadDeserializeError {
+ ContentType(content::Type),
+ BlockNotFound(block::content::TryFromU16Error),
+ UnitNotFound(unit::TryFromU16Error),
}
-impl PayloadDeserializeError
-{
- pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, DeserializeError>
- {
- match result
- {
- Ok(v) => Ok(v),
- Err(e) => Err(DeserializeError::Custom(Box::new(e.into()))),
- }
- }
+impl PayloadDeserializeError {
+ pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, DeserializeError> {
+ match result {
+ Ok(v) => Ok(v),
+ Err(e) => Err(DeserializeError::Custom(Box::new(e.into()))),
+ }
+ }
}
-impl From<block::content::TryFromU16Error> for PayloadDeserializeError
-{
- fn from(err: block::content::TryFromU16Error) -> Self
- {
- Self::BlockNotFound(err)
- }
+impl From<block::content::TryFromU16Error> for PayloadDeserializeError {
+ fn from(err: block::content::TryFromU16Error) -> Self {
+ Self::BlockNotFound(err)
+ }
}
-impl From<unit::TryFromU16Error> for PayloadDeserializeError
-{
- fn from(err: unit::TryFromU16Error) -> Self
- {
- Self::UnitNotFound(err)
- }
+impl From<unit::TryFromU16Error> for PayloadDeserializeError {
+ fn from(err: unit::TryFromU16Error) -> Self {
+ Self::UnitNotFound(err)
+ }
}
-impl fmt::Display for PayloadDeserializeError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::ContentType(have) => write!(f, "expected content {:?} or {:?} but got {have:?}", content::Type::Block, content::Type::Unit),
- Self::BlockNotFound(..) => f.write_str("payload block not found"),
- Self::UnitNotFound(..) => f.write_str("payload unit not found"),
- }
- }
+impl fmt::Display for PayloadDeserializeError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::ContentType(have) => write!(
+ f,
+ "expected content {:?} or {:?} but got {have:?}",
+ content::Type::Block,
+ content::Type::Unit
+ ),
+ Self::BlockNotFound(..) => f.write_str("payload block not found"),
+ Self::UnitNotFound(..) => f.write_str("payload unit not found"),
+ }
+ }
}
-impl Error for PayloadDeserializeError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::BlockNotFound(e) => Some(e),
- Self::UnitNotFound(e) => Some(e),
- _ => None,
- }
- }
+impl Error for PayloadDeserializeError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::BlockNotFound(e) => Some(e),
+ Self::UnitNotFound(e) => Some(e),
+ _ => None,
+ }
+ }
}
diff --git a/src/block/power.rs b/src/block/power.rs
index 8a7576b..3e152ef 100644
--- a/src/block/power.rs
+++ b/src/block/power.rs
@@ -2,167 +2,151 @@ use std::any::Any;
use std::error::Error;
use std::fmt;
-use crate::block::{BlockLogic, DataConvertError, DeserializeError, make_register, SerializeError};
-use crate::block::simple::{BuildCost, cost, SimpleBlock, state_impl};
-use crate::data::GridPos;
+use crate::block::simple::{cost, state_impl, BuildCost, SimpleBlock};
+use crate::block::{make_register, BlockLogic, DataConvertError, DeserializeError, SerializeError};
use crate::data::dynamic::{DynData, DynType};
+use crate::data::GridPos;
use crate::item::storage::Storage;
make_register!
(
- POWER_NODE: "power-node" => ConnectorBlock::new(1, true, cost!(Copper: 1, Lead: 3), 10);
- POWER_NODE_LARGE: "power-node-large" => ConnectorBlock::new(2, true, cost!(Lead: 10, Titanium: 5, Silicon: 3), 15);
- SURGE_TOWER: "surge-tower" => ConnectorBlock::new(2, true, cost!(Lead: 10, Titanium: 7, Silicon: 15, SurgeAlloy: 15), 2);
- DIODE: "diode" => SimpleBlock::new(1, false, cost!(Metaglass: 10, Silicon: 10, Plastanium: 5));
- BATTERY: "battery" => SimpleBlock::new(1, true, cost!(Copper: 5, Lead: 20));
- BATTERY_LARGE: "battery-large" => SimpleBlock::new(3, true, cost!(Lead: 50, Titanium: 20, Silicon: 30));
- COMBUSTION_GENERATOR: "combustion-generator" => SimpleBlock::new(1, true, cost!(Copper: 25, Lead: 15));
- THERMAL_GENERATOR: "thermal-generator" => SimpleBlock::new(2, true, cost!(Copper: 40, Lead: 50, Metaglass: 40, Graphite: 35, Silicon: 35));
- STEAM_GENERATOR: "steam-generator" => SimpleBlock::new(2, true, cost!(Copper: 35, Lead: 40, Graphite: 25, Silicon: 30));
- DIFFERENTIAL_GENERATOR: "differential-generator" => SimpleBlock::new(3, true, cost!(Copper: 70, Lead: 100, Metaglass: 50, Titanium: 50, Silicon: 65));
- RTG_GENERATOR: "rtg-generator" => SimpleBlock::new(2, true, cost!(Lead: 100, Thorium: 50, Silicon: 75, Plastanium: 75, PhaseFabric: 25));
- SOLAR_PANEL: "solar-panel" => SimpleBlock::new(1, true, cost!(Lead: 10, Silicon: 15));
- SOLAR_PANEL_LARGE: "solar-panel-large" => SimpleBlock::new(3, true, cost!(Lead: 80, Silicon: 110, PhaseFabric: 15));
- THORIUM_REACTOR: "thorium-reactor" => SimpleBlock::new(3, true, cost!(Lead: 300, Metaglass: 50, Graphite: 150, Thorium: 150, Silicon: 200));
- IMPACT_REACTOR: "impact-reactor" => SimpleBlock::new(4, true,
- cost!(Lead: 500, Metaglass: 250, Graphite: 400, Thorium: 100, Silicon: 300, SurgeAlloy: 250));
- // sandbox only
- POWER_SOURCE: "power-source" => ConnectorBlock::new(1, true, &[], 100);
- POWER_VOID: "power-void" => SimpleBlock::new(1, true, &[]);
+ "power-node" => ConnectorBlock::new(1, true, cost!(Copper: 1, Lead: 3), 10);
+ "power-node-large" => ConnectorBlock::new(2, true, cost!(Lead: 10, Titanium: 5, Silicon: 3), 15);
+ "surge-tower" => ConnectorBlock::new(2, true, cost!(Lead: 10, Titanium: 7, Silicon: 15, SurgeAlloy: 15), 2);
+ "diode" => SimpleBlock::new(1, false, cost!(Metaglass: 10, Silicon: 10, Plastanium: 5));
+ "battery" => SimpleBlock::new(1, true, cost!(Copper: 5, Lead: 20));
+ "battery-large" => SimpleBlock::new(3, true, cost!(Lead: 50, Titanium: 20, Silicon: 30));
+ "combustion-generator" => SimpleBlock::new(1, true, cost!(Copper: 25, Lead: 15));
+ "thermal-generator" => SimpleBlock::new(2, true, cost!(Copper: 40, Lead: 50, Metaglass: 40, Graphite: 35, Silicon: 35));
+ "steam-generator" => SimpleBlock::new(2, true, cost!(Copper: 35, Lead: 40, Graphite: 25, Silicon: 30));
+ "differential-generator" => SimpleBlock::new(3, true, cost!(Copper: 70, Lead: 100, Metaglass: 50, Titanium: 50, Silicon: 65));
+ "rtg-generator" => SimpleBlock::new(2, true, cost!(Lead: 100, Thorium: 50, Silicon: 75, Plastanium: 75, PhaseFabric: 25));
+ "solar-panel" => SimpleBlock::new(1, true, cost!(Lead: 10, Silicon: 15));
+ "solar-panel-large" => SimpleBlock::new(3, true, cost!(Lead: 80, Silicon: 110, PhaseFabric: 15));
+ "thorium-reactor" => SimpleBlock::new(3, true, cost!(Lead: 300, Metaglass: 50, Graphite: 150, Thorium: 150, Silicon: 200));
+ "impact-reactor" => SimpleBlock::new(4, true,
+ cost!(Lead: 500, Metaglass: 250, Graphite: 400, Thorium: 100, Silicon: 300, SurgeAlloy: 250));
+ // sandbox only
+ "power-source" => ConnectorBlock::new(1, true, &[], 100);
+ "power-void" => SimpleBlock::new(1, true, &[]);
);
-pub struct ConnectorBlock
-{
- size: u8,
- symmetric: bool,
- build_cost: BuildCost,
- max: u8,
+pub struct ConnectorBlock {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
+ max: u8,
}
-impl ConnectorBlock
-{
- pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost, max: u8) -> Self
- {
- if size == 0
- {
- panic!("invalid size");
- }
- if max == 0 || max > i8::MAX as u8
- {
- panic!("invalid maximum link count");
- }
- Self{size, symmetric, build_cost, max}
- }
-
- pub fn get_max_links(&self) -> u8
- {
- self.max
- }
-
- state_impl!(pub Vec<(i16, i16)>);
+impl ConnectorBlock {
+ pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost, max: u8) -> Self {
+ if size == 0 {
+ panic!("invalid size");
+ }
+ if max == 0 || max > i8::MAX as u8 {
+ panic!("invalid maximum link count");
+ }
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ max,
+ }
+ }
+
+ pub fn get_max_links(&self) -> u8 {
+ self.max
+ }
+
+ state_impl!(pub Vec<(i16, i16)>);
}
-impl BlockLogic for ConnectorBlock
-{
- fn get_size(&self) -> u8
- {
- self.size
- }
-
- fn is_symmetric(&self) -> bool
- {
- self.symmetric
- }
-
- fn create_build_cost(&self) -> Option<Storage>
- {
- if !self.build_cost.is_empty()
- {
- let mut storage = Storage::new();
- for (ty, cnt) in self.build_cost
- {
- storage.add(*ty, *cnt, u32::MAX);
- }
- Some(storage)
- }
- else {None}
- }
-
- fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError>
- {
- Ok(DynData::Empty)
- }
-
- fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError>
- {
- match data
- {
- DynData::Empty => Ok(Some(Self::create_state(Vec::new()))),
- DynData::Point2Array(s) =>
- {
- Ok(Some(Self::create_state(s)))
- },
- _ => Err(DeserializeError::InvalidType{have: data.get_type(), expect: DynType::Boolean}),
- }
- }
-
- fn clone_state(&self, state: &dyn Any) -> Box<dyn Any>
- {
- Box::new(Self::get_state(state).clone())
- }
-
- fn mirror_state(&self, state: &mut dyn Any, horizontally: bool, vertically: bool)
- {
- for (dx, dy) in Self::get_state_mut(state).iter_mut()
- {
- if horizontally {*dx = -*dx;}
- if vertically {*dy = -*dy;}
- }
- }
-
- fn rotate_state(&self, state: &mut dyn Any, clockwise: bool)
- {
- for (dx, dy) in Self::get_state_mut(state).iter_mut()
- {
- let (cdx, cdy) = (*dx, *dy);
- *dx = if clockwise {cdy} else {-cdy};
- *dy = if clockwise {-cdx} else {cdx};
- }
- }
-
- fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError>
- {
- Ok(DynData::Point2Array(Self::get_state(state).clone()))
- }
+impl BlockLogic for ConnectorBlock {
+ fn get_size(&self) -> u8 {
+ self.size
+ }
+
+ fn is_symmetric(&self) -> bool {
+ self.symmetric
+ }
+
+ fn create_build_cost(&self) -> Option<Storage> {
+ if !self.build_cost.is_empty() {
+ let mut storage = Storage::new();
+ for (ty, cnt) in self.build_cost {
+ storage.add(*ty, *cnt, u32::MAX);
+ }
+ Some(storage)
+ } else {
+ None
+ }
+ }
+
+ fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ Ok(DynData::Empty)
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> {
+ match data {
+ DynData::Empty => Ok(Some(Self::create_state(Vec::new()))),
+ DynData::Point2Array(s) => Ok(Some(Self::create_state(s))),
+ _ => Err(DeserializeError::InvalidType {
+ have: data.get_type(),
+ expect: DynType::Boolean,
+ }),
+ }
+ }
+
+ fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> {
+ Box::new(Self::get_state(state).clone())
+ }
+
+ fn mirror_state(&self, state: &mut dyn Any, horizontally: bool, vertically: bool) {
+ for (dx, dy) in Self::get_state_mut(state).iter_mut() {
+ if horizontally {
+ *dx = -*dx;
+ }
+ if vertically {
+ *dy = -*dy;
+ }
+ }
+ }
+
+ fn rotate_state(&self, state: &mut dyn Any, clockwise: bool) {
+ for (dx, dy) in Self::get_state_mut(state).iter_mut() {
+ let (cdx, cdy) = (*dx, *dy);
+ *dx = if clockwise { cdy } else { -cdy };
+ *dy = if clockwise { -cdx } else { cdx };
+ }
+ }
+
+ fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> {
+ Ok(DynData::Point2Array(Self::get_state(state).clone()))
+ }
}
#[derive(Debug)]
-pub enum ConnectorDeserializeError
-{
- LinkCount{have: usize, max: u8},
+pub enum ConnectorDeserializeError {
+ LinkCount { have: usize, max: u8 },
}
-impl ConnectorDeserializeError
-{
- pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, DeserializeError>
- {
- match result
- {
- Ok(v) => Ok(v),
- Err(e) => Err(DeserializeError::Custom(Box::new(e.into()))),
- }
- }
+impl ConnectorDeserializeError {
+ pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, DeserializeError> {
+ match result {
+ Ok(v) => Ok(v),
+ Err(e) => Err(DeserializeError::Custom(Box::new(e.into()))),
+ }
+ }
}
-impl fmt::Display for ConnectorDeserializeError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::LinkCount{have, max} => write!(f, "too many links ({have} but only {max} supported)"),
- }
- }
+impl fmt::Display for ConnectorDeserializeError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::LinkCount { have, max } => {
+ write!(f, "too many links ({have} but only {max} supported)")
+ }
+ }
+ }
}
impl Error for ConnectorDeserializeError {}
diff --git a/src/block/simple.rs b/src/block/simple.rs
index 96f6b77..5e48510 100644
--- a/src/block/simple.rs
+++ b/src/block/simple.rs
@@ -1,8 +1,8 @@
-use std::any::{Any, type_name};
+use std::any::{type_name, Any};
use crate::block::{BlockLogic, DataConvertError, DeserializeError, SerializeError};
-use crate::data::GridPos;
use crate::data::dynamic::DynData;
+use crate::data::GridPos;
use crate::item;
use crate::item::storage::Storage;
@@ -15,13 +15,13 @@ macro_rules!state_impl
{
state.downcast_ref::<$type>().unwrap()
}
-
+
$vis fn get_state_mut<'l>(state: &'l mut dyn Any) -> &'l mut $type
where Self: Sized
{
state.downcast_mut::<$type>().unwrap()
}
-
+
fn create_state(val: $type) -> Box<dyn Any>
where Self: Sized
{
@@ -33,80 +33,69 @@ pub(crate) use state_impl;
pub type BuildCost = &'static [(item::Type, u32)];
-pub struct SimpleBlock
-{
- size: u8,
- symmetric: bool,
- build_cost: BuildCost,
+pub struct SimpleBlock {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
}
-impl SimpleBlock
-{
- pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self
- {
- if size == 0
- {
- panic!("invalid size");
- }
- Self{size, symmetric, build_cost}
- }
+impl SimpleBlock {
+ pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self {
+ if size == 0 {
+ panic!("invalid size");
+ }
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ }
+ }
}
-impl BlockLogic for SimpleBlock
-{
- fn get_size(&self) -> u8
- {
- self.size
- }
-
- fn is_symmetric(&self) -> bool
- {
- self.symmetric
- }
-
- fn create_build_cost(&self) -> Option<Storage>
- {
- if !self.build_cost.is_empty()
- {
- let mut storage = Storage::new();
- for (ty, cnt) in self.build_cost
- {
- storage.add(*ty, *cnt, u32::MAX);
- }
- Some(storage)
- }
- else {None}
- }
-
- fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError>
- {
- Ok(DynData::Empty)
- }
-
- fn deserialize_state(&self, _: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError>
- {
- Ok(None)
- }
-
- fn clone_state(&self, _: &dyn Any) -> Box<dyn Any>
- {
- panic!("{} has no custom state", type_name::<Self>())
- }
-
- fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool)
- {
- panic!("{} has no custom state", type_name::<Self>());
- }
-
- fn rotate_state(&self, _: &mut dyn Any, _: bool)
- {
- panic!("{} has no custom state", type_name::<Self>());
- }
-
- fn serialize_state(&self, _: &dyn Any) -> Result<DynData, SerializeError>
- {
- Ok(DynData::Empty)
- }
+impl BlockLogic for SimpleBlock {
+ fn get_size(&self) -> u8 {
+ self.size
+ }
+
+ fn is_symmetric(&self) -> bool {
+ self.symmetric
+ }
+
+ fn create_build_cost(&self) -> Option<Storage> {
+ if !self.build_cost.is_empty() {
+ let mut storage = Storage::new();
+ for (ty, cnt) in self.build_cost {
+ storage.add(*ty, *cnt, u32::MAX);
+ }
+ Some(storage)
+ } else {
+ None
+ }
+ }
+
+ fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ Ok(DynData::Empty)
+ }
+
+ fn deserialize_state(&self, _: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> {
+ Ok(None)
+ }
+
+ fn clone_state(&self, _: &dyn Any) -> Box<dyn Any> {
+ panic!("{} has no custom state", type_name::<Self>())
+ }
+
+ fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool) {
+ panic!("{} has no custom state", type_name::<Self>());
+ }
+
+ fn rotate_state(&self, _: &mut dyn Any, _: bool) {
+ panic!("{} has no custom state", type_name::<Self>());
+ }
+
+ fn serialize_state(&self, _: &dyn Any) -> Result<DynData, SerializeError> {
+ Ok(DynData::Empty)
+ }
}
macro_rules!cost
diff --git a/src/block/transport.rs b/src/block/transport.rs
index bba7408..a889c71 100644
--- a/src/block/transport.rs
+++ b/src/block/transport.rs
@@ -2,353 +2,320 @@ use std::any::Any;
use std::error::Error;
use std::fmt;
-use crate::block::{BlockLogic, DataConvertError, DeserializeError, make_register, SerializeError};
-use crate::block::simple::{BuildCost, cost, SimpleBlock, state_impl};
+use crate::block::simple::{cost, state_impl, BuildCost, SimpleBlock};
+use crate::block::{make_register, BlockLogic, DataConvertError, DeserializeError, SerializeError};
use crate::content;
-use crate::data::GridPos;
use crate::data::dynamic::{DynData, DynType};
+use crate::data::GridPos;
use crate::item;
use crate::item::storage::Storage;
make_register!
(
- CONVEYOR: "conveyor" => SimpleBlock::new(1, false, cost!(Copper: 1));
- TITANIUM_CONVEYOR: "titanium-conveyor" => SimpleBlock::new(1, false, cost!(Copper: 1, Lead: 1, Titanium: 1));
- PLASTANIUM_CONVEYOR: "plastanium-conveyor" => SimpleBlock::new(1, false, cost!(Graphite: 1, Silicon: 1, Plastanium: 1));
- ARMORED_CONVEYOR: "armored-conveyor" => SimpleBlock::new(1, false, cost!(Metaglass: 1, Thorium: 1, Plastanium: 1));
- JUNCTION: "junction" => SimpleBlock::new(1, true, cost!(Copper: 2));
- BRIDGE_CONVEYOR: "bridge-conveyor" => BridgeBlock::new(1, false, cost!(Copper: 6, Lead: 6), 4, true);
- PHASE_CONVEYOR: "phase-conveyor" => BridgeBlock::new(1, false, cost!(Lead: 10, Graphite: 10, Silicon: 7, PhaseFabric: 5), 12, true);
- SORTER: "sorter" => ItemBlock::new(1, true, cost!(Copper: 2, Lead: 2));
- INVERTED_SORTER: "inverted-sorter" => ItemBlock::new(1, true, cost!(Copper: 2, Lead: 2));
- ROUTER: "router" => SimpleBlock::new(1, true, cost!(Copper: 3));
- DISTRIBUTOR: "distributor" => SimpleBlock::new(2, true, cost!(Copper: 4, Lead: 4));
- OVERFLOW_GATE: "overflow-gate" => SimpleBlock::new(1, true, cost!(Copper: 4, Lead: 2));
- UNDERFLOW_GATE: "underflow-gate" => SimpleBlock::new(1, true, cost!(Copper: 4, Lead: 2));
- MASS_DRIVER: "mass-driver" => BridgeBlock::new(3, true, cost!(Lead: 125, Titanium: 125, Thorium: 50, Silicon: 75), 55, false);
- // sandbox only
- ITEM_SOURCE: "item-source" => ItemBlock::new(1, true, &[]);
- ITEM_VOID: "item-void" => SimpleBlock::new(1, true, &[]);
+ "conveyor" => SimpleBlock::new(1, false, cost!(Copper: 1));
+ "titanium-conveyor" => SimpleBlock::new(1, false, cost!(Copper: 1, Lead: 1, Titanium: 1));
+ "plastanium-conveyor" => SimpleBlock::new(1, false, cost!(Graphite: 1, Silicon: 1, Plastanium: 1));
+ "armored-conveyor" => SimpleBlock::new(1, false, cost!(Metaglass: 1, Thorium: 1, Plastanium: 1));
+ "junction" => SimpleBlock::new(1, true, cost!(Copper: 2));
+ "bridge-conveyor" => BridgeBlock::new(1, false, cost!(Copper: 6, Lead: 6), 4, true);
+ "phase-conveyor" => BridgeBlock::new(1, false, cost!(Lead: 10, Graphite: 10, Silicon: 7, PhaseFabric: 5), 12, true);
+ "sorter" => ItemBlock::new(1, true, cost!(Copper: 2, Lead: 2));
+ "inverted-sorter" => ItemBlock::new(1, true, cost!(Copper: 2, Lead: 2));
+ "router" => SimpleBlock::new(1, true, cost!(Copper: 3));
+ "distributor" => SimpleBlock::new(2, true, cost!(Copper: 4, Lead: 4));
+ "overflow-gate" => SimpleBlock::new(1, true, cost!(Copper: 4, Lead: 2));
+ "underflow-gate" => SimpleBlock::new(1, true, cost!(Copper: 4, Lead: 2));
+ "mass-driver" => BridgeBlock::new(3, true, cost!(Lead: 125, Titanium: 125, Thorium: 50, Silicon: 75), 55, false);
+ // sandbox only
+ "item-source" => ItemBlock::new(1, true, &[]);
+ "item-void" => SimpleBlock::new(1, true, &[]);
);
-pub struct ItemBlock
-{
- size: u8,
- symmetric: bool,
- build_cost: BuildCost,
+pub struct ItemBlock {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
}
-impl ItemBlock
-{
- pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self
- {
- if size == 0
- {
- panic!("invalid size");
- }
- Self{size, symmetric, build_cost}
- }
-
- state_impl!(pub Option<item::Type>);
+impl ItemBlock {
+ pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self {
+ if size == 0 {
+ panic!("invalid size");
+ }
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ }
+ }
+
+ state_impl!(pub Option<item::Type>);
}
-impl BlockLogic for ItemBlock
-{
- fn get_size(&self) -> u8
- {
- self.size
- }
-
- fn is_symmetric(&self) -> bool
- {
- self.symmetric
- }
-
- fn create_build_cost(&self) -> Option<Storage>
- {
- if !self.build_cost.is_empty()
- {
- let mut storage = Storage::new();
- for (ty, cnt) in self.build_cost
- {
- storage.add(*ty, *cnt, u32::MAX);
- }
- Some(storage)
- }
- else {None}
- }
-
- fn data_from_i32(&self, config: i32, _: GridPos) -> Result<DynData, DataConvertError>
- {
- if config < 0 || config > u16::MAX as i32
- {
- return Err(DataConvertError::Custom(Box::new(ItemConvertError(config))));
- }
- Ok(DynData::Content(content::Type::Item, config as u16))
- }
-
- fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError>
- {
- match data
- {
- DynData::Empty => Ok(Some(Self::create_state(None))),
- DynData::Content(content::Type::Item, id) => Ok(Some(Self::create_state(Some(ItemDeserializeError::forward(item::Type::try_from(id))?)))),
- DynData::Content(have, ..) => Err(DeserializeError::Custom(Box::new(ItemDeserializeError::ContentType(have)))),
- _ => Err(DeserializeError::InvalidType{have: data.get_type(), expect: DynType::Content}),
- }
- }
-
- fn clone_state(&self, state: &dyn Any) -> Box<dyn Any>
- {
- let state = Self::get_state(state);
- Box::new(Self::create_state(*state))
- }
-
- fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool)
- {
- }
-
- fn rotate_state(&self, _: &mut dyn Any, _: bool)
- {
- }
-
- fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError>
- {
- match Self::get_state(state)
- {
- None => Ok(DynData::Empty),
- Some(item) => Ok(DynData::Content(content::Type::Item, (*item).into())),
- }
- }
+impl BlockLogic for ItemBlock {
+ fn get_size(&self) -> u8 {
+ self.size
+ }
+
+ fn is_symmetric(&self) -> bool {
+ self.symmetric
+ }
+
+ fn create_build_cost(&self) -> Option<Storage> {
+ if !self.build_cost.is_empty() {
+ let mut storage = Storage::new();
+ for (ty, cnt) in self.build_cost {
+ storage.add(*ty, *cnt, u32::MAX);
+ }
+ Some(storage)
+ } else {
+ None
+ }
+ }
+
+ fn data_from_i32(&self, config: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ if config < 0 || config > u16::MAX as i32 {
+ return Err(DataConvertError::Custom(Box::new(ItemConvertError(config))));
+ }
+ Ok(DynData::Content(content::Type::Item, config as u16))
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> {
+ match data {
+ DynData::Empty => Ok(Some(Self::create_state(None))),
+ DynData::Content(content::Type::Item, id) => Ok(Some(Self::create_state(Some(
+ ItemDeserializeError::forward(item::Type::try_from(id))?,
+ )))),
+ DynData::Content(have, ..) => Err(DeserializeError::Custom(Box::new(
+ ItemDeserializeError::ContentType(have),
+ ))),
+ _ => Err(DeserializeError::InvalidType {
+ have: data.get_type(),
+ expect: DynType::Content,
+ }),
+ }
+ }
+
+ fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> {
+ let state = Self::get_state(state);
+ Box::new(Self::create_state(*state))
+ }
+
+ fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool) {}
+
+ fn rotate_state(&self, _: &mut dyn Any, _: bool) {}
+
+ fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> {
+ match Self::get_state(state) {
+ None => Ok(DynData::Empty),
+ Some(item) => Ok(DynData::Content(content::Type::Item, (*item).into())),
+ }
+ }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ItemConvertError(pub i32);
-impl fmt::Display for ItemConvertError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- write!(f, "invalid config ({}) for item", self.0)
- }
+impl fmt::Display for ItemConvertError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "invalid config ({}) for item", self.0)
+ }
}
impl Error for ItemConvertError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum ItemDeserializeError
-{
- ContentType(content::Type),
- NotFound(item::TryFromU16Error),
+pub enum ItemDeserializeError {
+ ContentType(content::Type),
+ NotFound(item::TryFromU16Error),
}
-impl ItemDeserializeError
-{
- pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, DeserializeError>
- {
- match result
- {
- Ok(v) => Ok(v),
- Err(e) => Err(DeserializeError::Custom(Box::new(e.into()))),
- }
- }
+impl ItemDeserializeError {
+ pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, DeserializeError> {
+ match result {
+ Ok(v) => Ok(v),
+ Err(e) => Err(DeserializeError::Custom(Box::new(e.into()))),
+ }
+ }
}
-impl From<item::TryFromU16Error> for ItemDeserializeError
-{
- fn from(err: item::TryFromU16Error) -> Self
- {
- Self::NotFound(err)
- }
+impl From<item::TryFromU16Error> for ItemDeserializeError {
+ fn from(err: item::TryFromU16Error) -> Self {
+ Self::NotFound(err)
+ }
}
-impl fmt::Display for ItemDeserializeError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::ContentType(have) => write!(f, "expected content {:?} but got {have:?}", content::Type::Item),
- Self::NotFound(..) => f.write_str("target item not found"),
- }
- }
+impl fmt::Display for ItemDeserializeError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::ContentType(have) => write!(
+ f,
+ "expected content {:?} but got {have:?}",
+ content::Type::Item
+ ),
+ Self::NotFound(..) => f.write_str("target item not found"),
+ }
+ }
}
-impl Error for ItemDeserializeError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::NotFound(e) => Some(e),
- _ => None,
- }
- }
+impl Error for ItemDeserializeError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::NotFound(e) => Some(e),
+ _ => None,
+ }
+ }
}
-pub struct BridgeBlock
-{
- size: u8,
- symmetric: bool,
- build_cost: BuildCost,
- range: u16,
- ortho: bool,
+pub struct BridgeBlock {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
+ range: u16,
+ ortho: bool,
}
type Point2 = (i32, i32);
-impl BridgeBlock
-{
- pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost, range: u16, ortho: bool) -> Self
- {
- if size == 0
- {
- panic!("invalid size");
- }
- if range == 0
- {
- panic!("invalid range");
- }
- Self{size, symmetric, build_cost, range, ortho}
- }
-
- state_impl!(pub Option<Point2>);
+impl BridgeBlock {
+ pub const fn new(
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
+ range: u16,
+ ortho: bool,
+ ) -> Self {
+ if size == 0 {
+ panic!("invalid size");
+ }
+ if range == 0 {
+ panic!("invalid range");
+ }
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ range,
+ ortho,
+ }
+ }
+
+ state_impl!(pub Option<Point2>);
}
-impl BlockLogic for BridgeBlock
-{
- fn get_size(&self) -> u8
- {
- self.size
- }
-
- fn is_symmetric(&self) -> bool
- {
- self.symmetric
- }
-
- fn create_build_cost(&self) -> Option<Storage>
- {
- if !self.build_cost.is_empty()
- {
- let mut storage = Storage::new();
- for (ty, cnt) in self.build_cost
- {
- storage.add(*ty, *cnt, u32::MAX);
- }
- Some(storage)
- }
- else {None}
- }
-
- fn data_from_i32(&self, config: i32, pos: GridPos) -> Result<DynData, DataConvertError>
- {
- let (x, y) = ((config >> 16) as i16, config as i16);
- if x < 0 || y < 0
- {
- return Err(DataConvertError::Custom(Box::new(BridgeConvertError{x, y})));
- }
- let dx = x as i32 - pos.0 as i32;
- let dy = y as i32 - pos.1 as i32;
- Ok(DynData::Point2(dx, dy))
- }
-
- fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError>
- {
- match data
- {
- DynData::Empty => Ok(Some(Self::create_state(None))),
- DynData::Point2(dx, dy) =>
- {
- if self.ortho
- {
- // the game uses (-worldX, -worldY) to indicate no target
- // likely because the absolute target being (0, 0) means it's unlinked
- if dx != 0
- {
- if dy != 0
- {
- return Ok(Some(Self::create_state(None)));
- }
- else
- {
- if dx > self.range as i32 || dx < -(self.range as i32)
- {
- return Ok(Some(Self::create_state(None)));
- }
- }
- }
- else
- {
- if dy > self.range as i32 || dy < -(self.range as i32)
- {
- return Ok(Some(Self::create_state(None)));
- }
- }
- }
- // can't check range otherwise, it depends on the target's size
- Ok(Some(Self::create_state(Some((dx, dy)))))
- },
- _ => Err(DeserializeError::InvalidType{have: data.get_type(), expect: DynType::Point2}),
- }
- }
-
- fn clone_state(&self, state: &dyn Any) -> Box<dyn Any>
- {
- let state = Self::get_state(state);
- Box::new(Self::create_state(*state))
- }
-
- fn mirror_state(&self, state: &mut dyn Any, horizontally: bool, vertically: bool)
- {
- match Self::get_state_mut(state)
- {
- None => (),
- Some((dx, dy)) =>
- {
- if horizontally {*dx = -*dx;}
- if vertically {*dy = -*dy;}
- },
- }
- }
-
- fn rotate_state(&self, state: &mut dyn Any, clockwise: bool)
- {
- match Self::get_state_mut(state)
- {
- None => (),
- Some((dx, dy)) =>
- {
- let (cdx, cdy) = (*dx, *dy);
- *dx = if clockwise {cdy} else {-cdy};
- *dy = if clockwise {-cdx} else {cdx};
- },
- }
- }
-
- fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError>
- {
- match Self::get_state(state)
- {
- None => Ok(DynData::Empty),
- Some((dx, dy)) => Ok(DynData::Point2(*dx, *dy)),
- }
- }
+impl BlockLogic for BridgeBlock {
+ fn get_size(&self) -> u8 {
+ self.size
+ }
+
+ fn is_symmetric(&self) -> bool {
+ self.symmetric
+ }
+
+ fn create_build_cost(&self) -> Option<Storage> {
+ if !self.build_cost.is_empty() {
+ let mut storage = Storage::new();
+ for (ty, cnt) in self.build_cost {
+ storage.add(*ty, *cnt, u32::MAX);
+ }
+ Some(storage)
+ } else {
+ None
+ }
+ }
+
+ fn data_from_i32(&self, config: i32, pos: GridPos) -> Result<DynData, DataConvertError> {
+ let (x, y) = ((config >> 16) as i16, config as i16);
+ if x < 0 || y < 0 {
+ return Err(DataConvertError::Custom(Box::new(BridgeConvertError {
+ x,
+ y,
+ })));
+ }
+ let dx = x as i32 - pos.0 as i32;
+ let dy = y as i32 - pos.1 as i32;
+ Ok(DynData::Point2(dx, dy))
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> {
+ match data {
+ DynData::Empty => Ok(Some(Self::create_state(None))),
+ DynData::Point2(dx, dy) => {
+ if self.ortho {
+ // the game uses (-worldX, -worldY) to indicate no target
+ // likely because the absolute target being (0, 0) means it's unlinked
+ if dx != 0 {
+ if dy != 0 {
+ return Ok(Some(Self::create_state(None)));
+ } else {
+ if dx > self.range as i32 || dx < -(self.range as i32) {
+ return Ok(Some(Self::create_state(None)));
+ }
+ }
+ } else {
+ if dy > self.range as i32 || dy < -(self.range as i32) {
+ return Ok(Some(Self::create_state(None)));
+ }
+ }
+ }
+ // can't check range otherwise, it depends on the target's size
+ Ok(Some(Self::create_state(Some((dx, dy)))))
+ }
+ _ => Err(DeserializeError::InvalidType {
+ have: data.get_type(),
+ expect: DynType::Point2,
+ }),
+ }
+ }
+
+ fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> {
+ let state = Self::get_state(state);
+ Box::new(Self::create_state(*state))
+ }
+
+ fn mirror_state(&self, state: &mut dyn Any, horizontally: bool, vertically: bool) {
+ match Self::get_state_mut(state) {
+ None => (),
+ Some((dx, dy)) => {
+ if horizontally {
+ *dx = -*dx;
+ }
+ if vertically {
+ *dy = -*dy;
+ }
+ }
+ }
+ }
+
+ fn rotate_state(&self, state: &mut dyn Any, clockwise: bool) {
+ match Self::get_state_mut(state) {
+ None => (),
+ Some((dx, dy)) => {
+ let (cdx, cdy) = (*dx, *dy);
+ *dx = if clockwise { cdy } else { -cdy };
+ *dy = if clockwise { -cdx } else { cdx };
+ }
+ }
+ }
+
+ fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> {
+ match Self::get_state(state) {
+ None => Ok(DynData::Empty),
+ Some((dx, dy)) => Ok(DynData::Point2(*dx, *dy)),
+ }
+ }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub struct BridgeConvertError
-{
- pub x: i16,
- pub y: i16,
+pub struct BridgeConvertError {
+ pub x: i16,
+ pub y: i16,
}
-impl fmt::Display for BridgeConvertError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- write!(f, "invalid coordinate ({} / {}) for bridge", self.x, self.y)
- }
+impl fmt::Display for BridgeConvertError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "invalid coordinate ({} / {}) for bridge", self.x, self.y)
+ }
}
impl Error for BridgeConvertError {}
diff --git a/src/block/turret.rs b/src/block/turret.rs
index 61fd07d..6c91ce2 100644
--- a/src/block/turret.rs
+++ b/src/block/turret.rs
@@ -3,22 +3,22 @@ use crate::block::simple::{cost, SimpleBlock};
make_register!
(
- DUO: "duo" => SimpleBlock::new(1, true, cost!(Copper: 35));
- SCATTER: "scatter" => SimpleBlock::new(2, true, cost!(Copper: 85, Lead: 45));
- SCORCH: "scorch" => SimpleBlock::new(1, true, cost!(Copper: 25, Graphite: 22));
- HAIL: "hail" => SimpleBlock::new(1, true, cost!(Copper: 40, Graphite: 17));
- WAVE: "wave" => SimpleBlock::new(2, true, cost!(Copper: 25, Lead: 75, Metaglass: 45));
- LANCER: "lancer" => SimpleBlock::new(2, true, cost!(Copper: 60, Lead: 70, Titanium: 30, Silicon: 60));
- ARC: "arc" => SimpleBlock::new(1, true, cost!(Copper: 50, Lead: 50));
- PARALLAX: "parallax" => SimpleBlock::new(2, true, cost!(Graphite: 30, Titanium: 90, Silicon: 120));
- SWARMER: "swarmer" => SimpleBlock::new(2, true, cost!(Graphite: 35, Titanium: 35, Silicon: 30, Plastanium: 45));
- SALVO: "salvo" => SimpleBlock::new(2, true, cost!(Copper: 100, Graphite: 80, Titanium: 50));
- SEGMENT: "segment" => SimpleBlock::new(2, true, cost!(Titanium: 40, Thorium: 80, Silicon: 130, PhaseFabric: 40));
- TSUNAMI: "tsunami" => SimpleBlock::new(3, true, cost!(Lead: 400, Metaglass: 100, Titanium: 250, Thorium: 100));
- FUSE: "fuse" => SimpleBlock::new(3, true, cost!(Copper: 225, Graphite: 225, Thorium: 100));
- RIPPLE: "ripple" => SimpleBlock::new(3, true, cost!(Copper: 150, Graphite: 135, Titanium: 60));
- CYCLONE: "cyclone" => SimpleBlock::new(3, true, cost!(Copper: 200, Titanium: 125, Plastanium: 80));
- FORESHADOW: "foreshadow" => SimpleBlock::new(4, true, cost!(Copper: 1000, Metaglass: 600, Silicon: 600, Plastanium: 200, SurgeAlloy: 300));
- SPECTRE: "spectre" => SimpleBlock::new(4, true, cost!(Copper: 900, Graphite: 300, Thorium: 250, Plastanium: 175, SurgeAlloy: 250));
- MELTDOWN: "meltdown" => SimpleBlock::new(4, true, cost!(Copper: 1200, Lead: 350, Graphite: 300, Silicon: 325, SurgeAlloy: 325));
+ "duo" => SimpleBlock::new(1, true, cost!(Copper: 35));
+ "scatter" => SimpleBlock::new(2, true, cost!(Copper: 85, Lead: 45));
+ "scorch" => SimpleBlock::new(1, true, cost!(Copper: 25, Graphite: 22));
+ "hail" => SimpleBlock::new(1, true, cost!(Copper: 40, Graphite: 17));
+ "wave" => SimpleBlock::new(2, true, cost!(Copper: 25, Lead: 75, Metaglass: 45));
+ "lancer" => SimpleBlock::new(2, true, cost!(Copper: 60, Lead: 70, Titanium: 30, Silicon: 60));
+ "arc" => SimpleBlock::new(1, true, cost!(Copper: 50, Lead: 50));
+ "parallax" => SimpleBlock::new(2, true, cost!(Graphite: 30, Titanium: 90, Silicon: 120));
+ "swarmer" => SimpleBlock::new(2, true, cost!(Graphite: 35, Titanium: 35, Silicon: 30, Plastanium: 45));
+ "salvo" => SimpleBlock::new(2, true, cost!(Copper: 100, Graphite: 80, Titanium: 50));
+ "segment" => SimpleBlock::new(2, true, cost!(Titanium: 40, Thorium: 80, Silicon: 130, PhaseFabric: 40));
+ "tsunami" => SimpleBlock::new(3, true, cost!(Lead: 400, Metaglass: 100, Titanium: 250, Thorium: 100));
+ "fuse" => SimpleBlock::new(3, true, cost!(Copper: 225, Graphite: 225, Thorium: 100));
+ "ripple" => SimpleBlock::new(3, true, cost!(Copper: 150, Graphite: 135, Titanium: 60));
+ "cyclone" => SimpleBlock::new(3, true, cost!(Copper: 200, Titanium: 125, Plastanium: 80));
+ "foreshadow" => SimpleBlock::new(4, true, cost!(Copper: 1000, Metaglass: 600, Silicon: 600, Plastanium: 200, SurgeAlloy: 300));
+ "spectre" => SimpleBlock::new(4, true, cost!(Copper: 900, Graphite: 300, Thorium: 250, Plastanium: 175, SurgeAlloy: 250));
+ "meltdown" => SimpleBlock::new(4, true, cost!(Copper: 1200, Lead: 350, Graphite: 300, Silicon: 325, SurgeAlloy: 325));
);
diff --git a/src/content.rs b/src/content.rs
index 5f04dc8..3172096 100644
--- a/src/content.rs
+++ b/src/content.rs
@@ -1,177 +1,148 @@
use std::error::Error;
-macro_rules!numeric_enum
-{
+macro_rules! numeric_enum {
($vis:vis enum $tname:ident for $numeric:ty | $error:ident {$($name:ident $(= $val:literal)?),* $(,)?}) =>
{
crate::content::numeric_enum!($vis enum $tname for $numeric | $error* {$($name $(= $val)?),*});
-
- impl std::fmt::Display for $error
- {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
- {
+
+ impl std::fmt::Display for $error {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "no variant of {} for value {}", stringify!($tname), self.0)
}
}
-
+
impl std::error::Error for $error {}
};
($vis:vis enum $tname:ident for $numeric:ty | $error:ident* {$($name:ident $(= $val:literal)?),* $(,)?}) =>
{
#[repr($numeric)]
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
- $vis enum $tname
- {
- $($name $(= $val)?,)+
- }
-
+ $vis enum $tname { $($name $(= $val)?,)+ }
+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
$vis struct $error($vis $numeric);
-
- impl TryFrom<$numeric> for $tname
- {
+
+ impl TryFrom<$numeric> for $tname {
type Error = $error;
-
+
#[allow(non_upper_case_globals)]
- fn try_from(value: $numeric) -> Result<Self, $error>
- {
+ fn try_from(value: $numeric) -> Result<Self, $error> {
$(const $name: $numeric = $tname::$name as $numeric;)+
- match value
- {
+ match value {
$($name => Ok(Self::$name),)+
_ => Err($error(value)),
}
}
}
-
- impl From<$tname> for $numeric
- {
- fn from(value: $tname) -> $numeric
- {
- value as $numeric
- }
- }
+
+ impl From<$tname> for $numeric { fn from(value: $tname) -> $numeric { value as $numeric } }
};
}
+
pub(crate) use numeric_enum;
-macro_rules!content_enum
-{
- ($vis:vis enum $tname:ident / $ctype:ident for u16 | $error:ident {$($name:ident $(= $val:literal)? => $vname:expr),* $(,)?}) =>
+macro_rules! content_enum {
+ ($vis:vis enum $tname:ident / $ctype:ident for u16 | $error:ident {$($val:literal),* $(,)?}) =>
{
- $crate::content::numeric_enum!($vis enum $tname for u16 | $error* {$($name $(= $val)?),*});
-
- impl $crate::content::Content for $tname
- {
- fn get_type(&self) -> $crate::content::Type
- {
+ paste::paste! {
+ $crate::content::numeric_enum!($vis enum $tname for u16 | $error* {
+ $([<$val:camel>]),*,
+ });
+
+ impl $crate::content::Content for $tname {
+ fn get_type(&self) -> $crate::content::Type {
$crate::content::Type::$ctype
}
-
- fn get_id(&self) -> u16
- {
+
+ fn get_id(&self) -> u16 {
*self as u16
}
-
- fn get_name(&self) -> &'static str
- {
- match self
- {
- $(Self::$name => $vname,)*
+
+ fn get_name(&self) -> &'static str {
+ match self {
+ $(Self::[<$val:camel>] => $val,)*
}
}
}
-
+
impl std::fmt::Display for $error
{
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
- {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "no content of type {} for value {}", stringify!($ctype), self.0)
}
}
-
+
impl std::error::Error for $error {}
+ }
};
}
pub(crate) use content_enum;
-numeric_enum!
-{
- pub enum Type for u8 | TryFromU8Error
- {
- Item = 0,
- Block = 1,
- // Mech = 2,
- Bullet = 3,
- Fluid = 4,
- Modifier = 5,
- Unit = 6,
- Weather = 7,
- // Effect = 8,
- Sector = 9,
- // Loadout = 10,
- // TypeId = 11,
- // Error = 12,
- Planet = 13,
- // Ammo = 14,
- Team = 15,
- }
+numeric_enum! {
+ pub enum Type for u8 | TryFromU8Error
+ {
+ Item = 0,
+ Block = 1,
+ // Mech = 2,
+ Bullet = 3,
+ Fluid = 4,
+ Modifier = 5,
+ Unit = 6,
+ Weather = 7,
+ // Effect = 8,
+ Sector = 9,
+ // Loadout = 10,
+ // TypeId = 11,
+ // Error = 12,
+ Planet = 13,
+ // Ammo = 14,
+ Team = 15,
+ }
}
-macro_rules!gen_by_id
-{
- ($target:path, $id:expr) =>
- {
- match <$target>::try_from($id)
- {
- Ok(v) => Ok(Box::new(v)),
- Err(e) => Err(Box::new(e)),
- }
- };
+macro_rules! gen_by_id {
+ ($target:path, $id:expr) => {
+ match <$target>::try_from($id) {
+ Ok(v) => Ok(Box::new(v)),
+ Err(e) => Err(Box::new(e)),
+ }
+ };
}
-impl Type
-{
- pub fn get(&self, id: u16) -> Result<Box<dyn Content>, Box<dyn Error>>
- {
- match self
- {
- Self::Item => gen_by_id!(crate::item::Type, id),
- Self::Block => gen_by_id!(crate::block::content::Type, id),
- Self::Fluid => gen_by_id!(crate::fluid::Type, id),
- Self::Modifier => gen_by_id!(crate::modifier::Type, id),
- Self::Unit => gen_by_id!(crate::unit::Type, id),
- Self::Team => gen_by_id!(crate::team::Team, id),
- _ => Ok(Box::new(Generic(*self, id))),
- }
- }
+impl Type {
+ pub fn get(&self, id: u16) -> Result<Box<dyn Content>, Box<dyn Error>> {
+ match self {
+ Self::Item => gen_by_id!(crate::item::Type, id),
+ Self::Block => gen_by_id!(crate::block::content::Type, id),
+ Self::Fluid => gen_by_id!(crate::fluid::Type, id),
+ Self::Modifier => gen_by_id!(crate::modifier::Type, id),
+ Self::Unit => gen_by_id!(crate::unit::Type, id),
+ Self::Team => gen_by_id!(crate::team::Team, id),
+ _ => Ok(Box::new(Generic(*self, id))),
+ }
+ }
}
-pub trait Content
-{
- fn get_type(&self) -> Type;
-
- fn get_id(&self) -> u16;
-
- fn get_name(&self) -> &str;
+pub trait Content {
+ fn get_type(&self) -> Type;
+
+ fn get_id(&self) -> u16;
+
+ fn get_name(&self) -> &str;
}
struct Generic(Type, u16);
-impl Content for Generic
-{
- fn get_type(&self) -> Type
- {
- self.0
- }
-
- fn get_id(&self) -> u16
- {
- self.1
- }
-
- fn get_name(&self) -> &str
- {
- "<unknown>"
- }
+impl Content for Generic {
+ fn get_type(&self) -> Type {
+ self.0
+ }
+
+ fn get_id(&self) -> u16 {
+ self.1
+ }
+
+ fn get_name(&self) -> &str {
+ "<unknown>"
+ }
}
diff --git a/src/data/base64.rs b/src/data/base64.rs
index ccd27ff..895a362 100644
--- a/src/data/base64.rs
+++ b/src/data/base64.rs
@@ -4,293 +4,339 @@ use std::fmt;
const CHARS: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const PADDING: u8 = b'=';
-fn decode_char(val: u8) -> Option<usize>
-{
- match val
- {
- b'A'..=b'Z' => Some((val - b'A') as usize),
- b'a'..=b'z' => Some(26 + (val - b'a') as usize),
- b'0'..=b'9' => Some(52 + (val - b'0') as usize),
- b'+' => Some(0x3E),
- b'/' => Some(0x3F),
- _ => None
- }
+fn decode_char(val: u8) -> Option<usize> {
+ match val {
+ b'A'..=b'Z' => Some((val - b'A') as usize),
+ b'a'..=b'z' => Some(26 + (val - b'a') as usize),
+ b'0'..=b'9' => Some(52 + (val - b'0') as usize),
+ b'+' => Some(0x3E),
+ b'/' => Some(0x3F),
+ _ => None,
+ }
}
-pub fn encode(input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError>
-{
- let use_pad = input.len() % 3 != 0;
- let expect_len = if use_pad {4 * (input.len() / 3 + 1)} else {4 * (input.len() / 3)};
- if output.len() < expect_len
- {
- return Err(EncodeError::Overflow{need: expect_len, have: output.len()});
- }
- let mut in_pos = 0usize;
- let mut out_pos = 0usize;
- while input.len() - in_pos >= 3
- {
- let buff = ((input[in_pos] as usize) << 16) | ((input[in_pos + 1] as usize) << 8) | (input[in_pos + 2] as usize);
- output[out_pos] = CHARS[buff >> 18];
- output[out_pos + 1] = CHARS[(buff >> 12) & 0x3F];
- output[out_pos + 2] = CHARS[(buff >> 6) & 0x3F];
- output[out_pos + 3] = CHARS[buff & 0x3F];
- out_pos += 4;
- in_pos += 3;
- }
- let remain = input.len() - in_pos;
- if remain > 0
- {
- let mut buff = (input[in_pos] as usize) << 16;
- if remain > 1
- {
- buff |= (input[in_pos + 1] as usize) << 8;
- }
- in_pos += if remain > 1 {2} else {1};
- output[out_pos] = CHARS[buff >> 18];
- output[out_pos + 1] = CHARS[(buff >> 12) & 0x3F];
- if remain > 1
- {
- output[out_pos + 2] = CHARS[(buff >> 6) & 0x3F];
- }
- else {output[out_pos + 2] = PADDING;}
- output[out_pos + 3] = PADDING;
- out_pos += 4;
- }
- assert_eq!(in_pos, input.len(), "missed input ({in_pos}, expected {})", input.len());
- assert_eq!(out_pos, expect_len, "missed output ({out_pos}, expected {expect_len})");
- Ok(out_pos)
+pub fn encode(input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
+ let use_pad = input.len() % 3 != 0;
+ let expect_len = if use_pad {
+ 4 * (input.len() / 3 + 1)
+ } else {
+ 4 * (input.len() / 3)
+ };
+ if output.len() < expect_len {
+ return Err(EncodeError::Overflow {
+ need: expect_len,
+ have: output.len(),
+ });
+ }
+ let mut in_pos = 0usize;
+ let mut out_pos = 0usize;
+ while input.len() - in_pos >= 3 {
+ let buff = ((input[in_pos] as usize) << 16)
+ | ((input[in_pos + 1] as usize) << 8)
+ | (input[in_pos + 2] as usize);
+ output[out_pos] = CHARS[buff >> 18];
+ output[out_pos + 1] = CHARS[(buff >> 12) & 0x3F];
+ output[out_pos + 2] = CHARS[(buff >> 6) & 0x3F];
+ output[out_pos + 3] = CHARS[buff & 0x3F];
+ out_pos += 4;
+ in_pos += 3;
+ }
+ let remain = input.len() - in_pos;
+ if remain > 0 {
+ let mut buff = (input[in_pos] as usize) << 16;
+ if remain > 1 {
+ buff |= (input[in_pos + 1] as usize) << 8;
+ }
+ in_pos += if remain > 1 { 2 } else { 1 };
+ output[out_pos] = CHARS[buff >> 18];
+ output[out_pos + 1] = CHARS[(buff >> 12) & 0x3F];
+ if remain > 1 {
+ output[out_pos + 2] = CHARS[(buff >> 6) & 0x3F];
+ } else {
+ output[out_pos + 2] = PADDING;
+ }
+ output[out_pos + 3] = PADDING;
+ out_pos += 4;
+ }
+ assert_eq!(
+ in_pos,
+ input.len(),
+ "missed input ({in_pos}, expected {})",
+ input.len()
+ );
+ assert_eq!(
+ out_pos, expect_len,
+ "missed output ({out_pos}, expected {expect_len})"
+ );
+ Ok(out_pos)
}
-macro_rules!do_decode
-{
- ($input:ident, $in_pos:expr) =>
- {
- match decode_char($input[$in_pos])
- {
- None => return Err(DecodeError::Malformed{at: $in_pos, value: $input[$in_pos]}),
- Some(v) => v,
- }
- };
+macro_rules! do_decode {
+ ($input:ident, $in_pos:expr) => {
+ match decode_char($input[$in_pos]) {
+ None => {
+ return Err(DecodeError::Malformed {
+ at: $in_pos,
+ value: $input[$in_pos],
+ })
+ }
+ Some(v) => v,
+ }
+ };
}
-pub fn decode(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError>
-{
- if input.len() % 4 != 0
- {
- // can't decode, but check for malformed data first
- let mut in_pad = false;
- for (i, &c) in input.iter().enumerate()
- {
- if c == PADDING
- {
- if i % 4 < 2
- {
- return Err(DecodeError::Malformed{at: i, value: c});
- }
- in_pad = true;
- }
- else if in_pad && i % 4 == 0
- {
- return Err(DecodeError::TrailingData{at: i});
- }
- else if (in_pad && i % 4 == 3) || decode_char(c).is_none()
- {
- return Err(DecodeError::Malformed{at: i, value: c});
- }
- }
- return Err(DecodeError::Truncated);
- }
- let pad_len = if input.len() > 0
- {
- if input[input.len() - 1] != PADDING {0}
- else if input[input.len() - 2] != PADDING {1}
- else {2}
- }
- else {0};
- let expect_len = input.len() / 4 * 3 - pad_len;
- if output.len() < expect_len
- {
- return Err(DecodeError::Overflow{need: expect_len, have: output.len()});
- }
- if !input.is_empty()
- {
- let mut in_pos = 0usize;
- let mut out_pos = 0usize;
- while in_pos < input.len() - 4
- {
- let c0 = do_decode!(input, in_pos);
- let c1 = do_decode!(input, in_pos + 1);
- let c2 = do_decode!(input, in_pos + 2);
- let c3 = do_decode!(input, in_pos + 3);
- let buff = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3;
- output[out_pos] = (buff >> 16) as u8;
- output[out_pos + 1] = (buff >> 8) as u8;
- output[out_pos + 2] = buff as u8;
- in_pos += 4;
- out_pos += 3;
- }
-
- let c0 = do_decode!(input, in_pos);
- let c1 = do_decode!(input, in_pos + 1);
- let mut buff = (c0 << 18) | (c1 << 12);
- output[out_pos] = (buff >> 16) as u8;
- out_pos += 1;
- if input[in_pos + 2] == PADDING
- {
- if input[in_pos + 3] != PADDING
- {
- return Err(DecodeError::Malformed{at: in_pos + 3, value: input[in_pos + 3]});
- }
- }
- else
- {
- buff |= do_decode!(input, in_pos + 2) << 6;
- output[out_pos] = (buff >> 8) as u8;
- out_pos += 1;
- if input[in_pos + 3] != PADDING
- {
- buff |= do_decode!(input, in_pos + 3);
- output[out_pos] = buff as u8;
- out_pos += 1;
- }
- }
- in_pos += 4;
-
- assert_eq!(in_pos, input.len(), "missed input ({in_pos}, expected {})", input.len());
- assert_eq!(out_pos, expect_len, "missed output ({out_pos}, expected {expect_len})");
- Ok(out_pos)
- }
- else {Ok(0)}
+pub fn decode(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
+ if input.len() % 4 != 0 {
+ // can't decode, but check for malformed data first
+ let mut in_pad = false;
+ for (i, &c) in input.iter().enumerate() {
+ if c == PADDING {
+ if i % 4 < 2 {
+ return Err(DecodeError::Malformed { at: i, value: c });
+ }
+ in_pad = true;
+ } else if in_pad && i % 4 == 0 {
+ return Err(DecodeError::TrailingData { at: i });
+ } else if (in_pad && i % 4 == 3) || decode_char(c).is_none() {
+ return Err(DecodeError::Malformed { at: i, value: c });
+ }
+ }
+ return Err(DecodeError::Truncated);
+ }
+ let pad_len = if input.len() > 0 {
+ if input[input.len() - 1] != PADDING {
+ 0
+ } else if input[input.len() - 2] != PADDING {
+ 1
+ } else {
+ 2
+ }
+ } else {
+ 0
+ };
+ let expect_len = input.len() / 4 * 3 - pad_len;
+ if output.len() < expect_len {
+ return Err(DecodeError::Overflow {
+ need: expect_len,
+ have: output.len(),
+ });
+ }
+ if !input.is_empty() {
+ let mut in_pos = 0usize;
+ let mut out_pos = 0usize;
+ while in_pos < input.len() - 4 {
+ let c0 = do_decode!(input, in_pos);
+ let c1 = do_decode!(input, in_pos + 1);
+ let c2 = do_decode!(input, in_pos + 2);
+ let c3 = do_decode!(input, in_pos + 3);
+ let buff = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3;
+ output[out_pos] = (buff >> 16) as u8;
+ output[out_pos + 1] = (buff >> 8) as u8;
+ output[out_pos + 2] = buff as u8;
+ in_pos += 4;
+ out_pos += 3;
+ }
+
+ let c0 = do_decode!(input, in_pos);
+ let c1 = do_decode!(input, in_pos + 1);
+ let mut buff = (c0 << 18) | (c1 << 12);
+ output[out_pos] = (buff >> 16) as u8;
+ out_pos += 1;
+ if input[in_pos + 2] == PADDING {
+ if input[in_pos + 3] != PADDING {
+ return Err(DecodeError::Malformed {
+ at: in_pos + 3,
+ value: input[in_pos + 3],
+ });
+ }
+ } else {
+ buff |= do_decode!(input, in_pos + 2) << 6;
+ output[out_pos] = (buff >> 8) as u8;
+ out_pos += 1;
+ if input[in_pos + 3] != PADDING {
+ buff |= do_decode!(input, in_pos + 3);
+ output[out_pos] = buff as u8;
+ out_pos += 1;
+ }
+ }
+ in_pos += 4;
+
+ assert_eq!(
+ in_pos,
+ input.len(),
+ "missed input ({in_pos}, expected {})",
+ input.len()
+ );
+ assert_eq!(
+ out_pos, expect_len,
+ "missed output ({out_pos}, expected {expect_len})"
+ );
+ Ok(out_pos)
+ } else {
+ Ok(0)
+ }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum DecodeError
-{
- Malformed{at: usize, value: u8},
- Overflow{need: usize, have: usize},
- Truncated,
- TrailingData{at: usize},
+pub enum DecodeError {
+ Malformed { at: usize, value: u8 },
+ Overflow { need: usize, have: usize },
+ Truncated,
+ TrailingData { at: usize },
}
-impl fmt::Display for DecodeError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Malformed{at, value} => write!(f, "malformed base64 character {value:?} (at {at})"),
- Self::Overflow{need, have} => write!(f, "decoder overflow (need {need}, but only have {have})"),
- Self::Truncated => f.write_str("truncated base64 input stream"),
- Self::TrailingData{at} => write!(f, "trailing data in base64 stream (at {at})"),
- }
- }
+impl fmt::Display for DecodeError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Malformed { at, value } => {
+ write!(f, "malformed base64 character {value:?} (at {at})")
+ }
+ Self::Overflow { need, have } => {
+ write!(f, "decoder overflow (need {need}, but only have {have})")
+ }
+ Self::Truncated => f.write_str("truncated base64 input stream"),
+ Self::TrailingData { at } => write!(f, "trailing data in base64 stream (at {at})"),
+ }
+ }
}
impl Error for DecodeError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum EncodeError
-{
- Overflow{need: usize, have: usize}
+pub enum EncodeError {
+ Overflow { need: usize, have: usize },
}
-impl fmt::Display for EncodeError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Overflow{need, have} => write!(f, "encoder overflow (need {need}, but only have {have})"),
- }
- }
+impl fmt::Display for EncodeError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Overflow { need, have } => {
+ write!(f, "encoder overflow (need {need}, but only have {have})")
+ }
+ }
+ }
}
impl Error for EncodeError {}
#[cfg(test)]
-mod test
-{
- use super::*;
-
- #[test]
- fn validate_chars()
- {
- for (i, &c0) in CHARS.iter().enumerate()
- {
- assert_ne!(c0, PADDING, "padding character in data charset at {i}");
- if i > 0
- {
- for (j, &c1) in CHARS[..i].iter().enumerate()
- {
- assert_ne!(c1, c0, "duplicate data character at {j} and {i}")
- }
- }
- }
- }
-
- #[test]
- fn decode_matches_chars()
- {
- for (i, &c0) in CHARS.iter().enumerate()
- {
- assert_eq!(decode_char(c0), Some(i), "data character {c0} (at {i}) isn't decoded properly");
- }
- }
-
- macro_rules!test_codec
- {
- ($func:ident, $input:expr => $expect:expr) =>
- {
- {
- const EMPTY: u8 = 0xC9u8; // arbitrary
- let mut output = [EMPTY; 64];
- let input = $input;
- let expect = $expect;
- assert_eq!($func(input, &mut output), Ok(expect.len()));
- assert_eq!(&output[..expect.len()], expect);
- assert!(output[expect.len()..].iter().all(|&x| x == EMPTY), "output buffer overflow");
- }
- };
- }
-
- #[test]
- fn encoder_success()
- {
- test_codec!(encode, b"Hello Wor" => b"SGVsbG8gV29y");
- test_codec!(encode, b"Hello Worl" => b"SGVsbG8gV29ybA==");
- test_codec!(encode, b"Hello World" => b"SGVsbG8gV29ybGQ=");
- test_codec!(encode, b"Hello World!" => b"SGVsbG8gV29ybGQh");
- }
-
- #[test]
- fn decoder_success()
- {
- test_codec!(decode, b"SGVsbG8gV29y" => b"Hello Wor");
- test_codec!(decode, b"SGVsbG8gV29ybA==" => b"Hello Worl");
- test_codec!(decode, b"SGVsbG8gV29ybGQ=" => b"Hello World");
- test_codec!(decode, b"SGVsbG8gV29ybGQh" => b"Hello World!");
- }
-
- #[test]
- fn encoder_fail()
- {
- let mut output = [0u8; 64];
- assert_eq!(encode(b"Hello Worl", &mut output[..15]), Err(EncodeError::Overflow{need: 16, have: 15}));
- assert_eq!(encode(b"Hello World!", &mut output[..0]), Err(EncodeError::Overflow{need: 16, have: 0}));
- }
-
- #[test]
- fn decoder_fail()
- {
- let mut output = [0u8; 64];
- assert_eq!(decode(b"SGVsbG8gV29ybA==", &mut output[..9]), Err(DecodeError::Overflow{need: 10, have: 9}));
- assert_eq!(decode(b"SGVsbG8gV29ybGQh", &mut output[..11]), Err(DecodeError::Overflow{need: 12, have: 11}));
- assert_eq!(decode(b"SGVsbG8gV29ybA", &mut output), Err(DecodeError::Truncated));
- assert_eq!(decode(b"SGVsbG8gV29yb", &mut output), Err(DecodeError::Truncated));
- assert_eq!(decode(b"SGVsbG8gV29y\n", &mut output), Err(DecodeError::Malformed{at: 12, value: b'\n'}));
- assert_eq!(decode(b"SGVs_bG8gV29y\n", &mut output), Err(DecodeError::Malformed{at: 4, value: b'_'}));
- assert_eq!(decode(b"SGVsbG8gV29ybA==*", &mut output), Err(DecodeError::TrailingData{at: 16}));
- assert_eq!(decode(b"SGVsbG8gV29ybA=*", &mut output), Err(DecodeError::Malformed{at: 15, value: b'*'}));
- assert_eq!(decode(b"SGVsbG8gV29ybA=A", &mut output), Err(DecodeError::Malformed{at: 15, value: b'A'}));
- }
+mod test {
+ use super::*;
+
+ #[test]
+ fn validate_chars() {
+ for (i, &c0) in CHARS.iter().enumerate() {
+ assert_ne!(c0, PADDING, "padding character in data charset at {i}");
+ if i > 0 {
+ for (j, &c1) in CHARS[..i].iter().enumerate() {
+ assert_ne!(c1, c0, "duplicate data character at {j} and {i}")
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn decode_matches_chars() {
+ for (i, &c0) in CHARS.iter().enumerate() {
+ assert_eq!(
+ decode_char(c0),
+ Some(i),
+ "data character {c0} (at {i}) isn't decoded properly"
+ );
+ }
+ }
+
+ macro_rules! test_codec {
+ ($func:ident, $input:expr => $expect:expr) => {{
+ const EMPTY: u8 = 0xC9u8; // arbitrary
+ let mut output = [EMPTY; 64];
+ let input = $input;
+ let expect = $expect;
+ assert_eq!($func(input, &mut output), Ok(expect.len()));
+ assert_eq!(&output[..expect.len()], expect);
+ assert!(
+ output[expect.len()..].iter().all(|&x| x == EMPTY),
+ "output buffer overflow"
+ );
+ }};
+ }
+
+ #[test]
+ fn encoder_success() {
+ test_codec!(encode, b"Hello Wor" => b"SGVsbG8gV29y");
+ test_codec!(encode, b"Hello Worl" => b"SGVsbG8gV29ybA==");
+ test_codec!(encode, b"Hello World" => b"SGVsbG8gV29ybGQ=");
+ test_codec!(encode, b"Hello World!" => b"SGVsbG8gV29ybGQh");
+ }
+
+ #[test]
+ fn decoder_success() {
+ test_codec!(decode, b"SGVsbG8gV29y" => b"Hello Wor");
+ test_codec!(decode, b"SGVsbG8gV29ybA==" => b"Hello Worl");
+ test_codec!(decode, b"SGVsbG8gV29ybGQ=" => b"Hello World");
+ test_codec!(decode, b"SGVsbG8gV29ybGQh" => b"Hello World!");
+ }
+
+ #[test]
+ fn encoder_fail() {
+ let mut output = [0u8; 64];
+ assert_eq!(
+ encode(b"Hello Worl", &mut output[..15]),
+ Err(EncodeError::Overflow { need: 16, have: 15 })
+ );
+ assert_eq!(
+ encode(b"Hello World!", &mut output[..0]),
+ Err(EncodeError::Overflow { need: 16, have: 0 })
+ );
+ }
+
+ #[test]
+ fn decoder_fail() {
+ let mut output = [0u8; 64];
+ assert_eq!(
+ decode(b"SGVsbG8gV29ybA==", &mut output[..9]),
+ Err(DecodeError::Overflow { need: 10, have: 9 })
+ );
+ assert_eq!(
+ decode(b"SGVsbG8gV29ybGQh", &mut output[..11]),
+ Err(DecodeError::Overflow { need: 12, have: 11 })
+ );
+ assert_eq!(
+ decode(b"SGVsbG8gV29ybA", &mut output),
+ Err(DecodeError::Truncated)
+ );
+ assert_eq!(
+ decode(b"SGVsbG8gV29yb", &mut output),
+ Err(DecodeError::Truncated)
+ );
+ assert_eq!(
+ decode(b"SGVsbG8gV29y\n", &mut output),
+ Err(DecodeError::Malformed {
+ at: 12,
+ value: b'\n'
+ })
+ );
+ assert_eq!(
+ decode(b"SGVs_bG8gV29y\n", &mut output),
+ Err(DecodeError::Malformed { at: 4, value: b'_' })
+ );
+ assert_eq!(
+ decode(b"SGVsbG8gV29ybA==*", &mut output),
+ Err(DecodeError::TrailingData { at: 16 })
+ );
+ assert_eq!(
+ decode(b"SGVsbG8gV29ybA=*", &mut output),
+ Err(DecodeError::Malformed {
+ at: 15,
+ value: b'*'
+ })
+ );
+ assert_eq!(
+ decode(b"SGVsbG8gV29ybA=A", &mut output),
+ Err(DecodeError::Malformed {
+ at: 15,
+ value: b'A'
+ })
+ );
+ }
}
diff --git a/src/data/command.rs b/src/data/command.rs
index 66d9822..ba146ff 100644
--- a/src/data/command.rs
+++ b/src/data/command.rs
@@ -1,9 +1,8 @@
use crate::content::numeric_enum;
-numeric_enum!
-{
- pub enum UnitCommand for u8 | TryFromU8Error
- {
- Attack, Rally, Idle
- }
+numeric_enum! {
+ pub enum UnitCommand for u8 | TryFromU8Error
+ {
+ Attack, Rally, Idle
+ }
}
diff --git a/src/data/dynamic.rs b/src/data/dynamic.rs
index 874dc6b..b1c4095 100644
--- a/src/data/dynamic.rs
+++ b/src/data/dynamic.rs
@@ -2,514 +2,460 @@ use std::error::Error;
use std::fmt;
use crate::content;
-use crate::data::{self, DataRead, DataWrite, GridPos, Serializer};
use crate::data::command::{self, UnitCommand};
+use crate::data::{self, DataRead, DataWrite, GridPos, Serializer};
use crate::logic::LogicField;
use crate::team::Team;
#[derive(Clone, Debug, PartialEq)]
-pub enum DynData
-{
- Empty,
- Int(i32),
- Long(i64),
- Float(f32),
- String(Option<String>),
- Content(content::Type, u16),
- IntArray(Vec<i32>),
- Point2(i32, i32),
- Point2Array(Vec<(i16, i16)>),
- TechNode(content::Type, u16),
- Boolean(bool),
- Double(f64),
- Building(GridPos),
- LogicField(LogicField),
- ByteArray(Vec<u8>),
- UnitCommand(UnitCommand),
- BoolArray(Vec<bool>),
- Unit(u32),
- Vec2Array(Vec<(f32, f32)>),
- Vec2(f32, f32),
- Team(Team),
+pub enum DynData {
+ Empty,
+ Int(i32),
+ Long(i64),
+ Float(f32),
+ String(Option<String>),
+ Content(content::Type, u16),
+ IntArray(Vec<i32>),
+ Point2(i32, i32),
+ Point2Array(Vec<(i16, i16)>),
+ TechNode(content::Type, u16),
+ Boolean(bool),
+ Double(f64),
+ Building(GridPos),
+ LogicField(LogicField),
+ ByteArray(Vec<u8>),
+ UnitCommand(UnitCommand),
+ BoolArray(Vec<bool>),
+ Unit(u32),
+ Vec2Array(Vec<(f32, f32)>),
+ Vec2(f32, f32),
+ Team(Team),
}
-impl DynData
-{
- pub fn get_type(&self) -> DynType
- {
- match self
- {
- Self::Empty => DynType::Empty,
- Self::Int(..) => DynType::Int,
- Self::Long(..) => DynType::Long,
- Self::Float(..) => DynType::Float,
- Self::String(..) => DynType::String,
- Self::Content(..) => DynType::Content,
- Self::IntArray(..) => DynType::IntArray,
- Self::Point2(..) => DynType::Point2,
- Self::Point2Array(..) => DynType::Point2Array,
- Self::TechNode(..) => DynType::TechNode,
- Self::Boolean(..) => DynType::Boolean,
- Self::Double(..) => DynType::Double,
- Self::Building(..) => DynType::Building,
- Self::LogicField(..) => DynType::LogicField,
- Self::ByteArray(..) => DynType::ByteArray,
- Self::UnitCommand(..) => DynType::UnitCommand,
- Self::BoolArray(..) => DynType::BoolArray,
- Self::Unit(..) => DynType::Unit,
- Self::Vec2Array(..) => DynType::Vec2Array,
- Self::Vec2(..) => DynType::Vec2,
- Self::Team(..) => DynType::Team,
- }
- }
+impl DynData {
+ pub fn get_type(&self) -> DynType {
+ match self {
+ Self::Empty => DynType::Empty,
+ Self::Int(..) => DynType::Int,
+ Self::Long(..) => DynType::Long,
+ Self::Float(..) => DynType::Float,
+ Self::String(..) => DynType::String,
+ Self::Content(..) => DynType::Content,
+ Self::IntArray(..) => DynType::IntArray,
+ Self::Point2(..) => DynType::Point2,
+ Self::Point2Array(..) => DynType::Point2Array,
+ Self::TechNode(..) => DynType::TechNode,
+ Self::Boolean(..) => DynType::Boolean,
+ Self::Double(..) => DynType::Double,
+ Self::Building(..) => DynType::Building,
+ Self::LogicField(..) => DynType::LogicField,
+ Self::ByteArray(..) => DynType::ByteArray,
+ Self::UnitCommand(..) => DynType::UnitCommand,
+ Self::BoolArray(..) => DynType::BoolArray,
+ Self::Unit(..) => DynType::Unit,
+ Self::Vec2Array(..) => DynType::Vec2Array,
+ Self::Vec2(..) => DynType::Vec2,
+ Self::Team(..) => DynType::Team,
+ }
+ }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum DynType
-{
- Empty, Int, Long, Float, String, Content, IntArray, Point2, Point2Array, TechNode, Boolean, Double, Building,
- LogicField, ByteArray, UnitCommand, BoolArray, Unit, Vec2Array, Vec2, Team,
+pub enum DynType {
+ Empty,
+ Int,
+ Long,
+ Float,
+ String,
+ Content,
+ IntArray,
+ Point2,
+ Point2Array,
+ TechNode,
+ Boolean,
+ Double,
+ Building,
+ LogicField,
+ ByteArray,
+ UnitCommand,
+ BoolArray,
+ Unit,
+ Vec2Array,
+ Vec2,
+ Team,
}
pub struct DynSerializer;
-impl Serializer<DynData> for DynSerializer
-{
- type ReadError = ReadError;
- type WriteError = WriteError;
-
- fn deserialize(&mut self, buff: &mut DataRead<'_>) -> Result<DynData, Self::ReadError>
- {
- match buff.read_u8()?
- {
- 0 => Ok(DynData::Empty),
- 1 => Ok(DynData::Int(buff.read_i32()?)),
- 2 => Ok(DynData::Long(buff.read_i64()?)),
- 3 => Ok(DynData::Float(buff.read_f32()?)),
- 4 =>
- {
- if buff.read_bool()?
- {
- Ok(DynData::String(Some(String::from(buff.read_utf()?))))
- }
- else {Ok(DynData::String(None))}
- },
- 5 => Ok(DynData::Content(content::Type::try_from(buff.read_u8()?)?, buff.read_u16()?)),
- 6 =>
- {
- let len = buff.read_i16()?;
- if len < 0
- {
- return Err(ReadError::IntArrayLen(len));
- }
- let mut result = Vec::<i32>::new();
- result.reserve(len as usize);
- for _ in 0..len
- {
- result.push(buff.read_i32()?);
- }
- Ok(DynData::IntArray(result))
- },
- 7 => Ok(DynData::Point2(buff.read_i32()?, buff.read_i32()?)),
- 8 =>
- {
- let len = buff.read_i8()?;
- if len < 0
- {
- return Err(ReadError::Point2ArrayLen(len));
- }
- let mut result = Vec::<(i16, i16)>::new();
- result.reserve(len as usize);
- for _ in 0..len
- {
- let pt = buff.read_i32()?;
- result.push(((pt >> 16) as i16, pt as i16));
- }
- Ok(DynData::Point2Array(result))
- },
- 9 => Ok(DynData::TechNode(content::Type::try_from(buff.read_u8()?)?, buff.read_u16()?)),
- 10 => Ok(DynData::Boolean(buff.read_bool()?)),
- 11 => Ok(DynData::Double(buff.read_f64()?)),
- 12 => Ok(DynData::Building(GridPos::from(buff.read_u32()?))),
- 13 =>
- {
- let id = buff.read_u8()?;
- match LogicField::try_from(id)
- {
- Ok(f) => Ok(DynData::LogicField(f)),
- Err(..) => Err(ReadError::LogicField(id)),
- }
- },
- 14 =>
- {
- let len = buff.read_i32()?;
- if len < 0
- {
- return Err(ReadError::ByteArrayLen(len));
- }
- let mut result = Vec::<u8>::new();
- buff.read_vec(&mut result, len as usize)?;
- Ok(DynData::ByteArray(result))
- },
- 15 =>
- {
- let id = buff.read_u8()?;
- match UnitCommand::try_from(id)
- {
- Ok(f) => Ok(DynData::UnitCommand(f)),
- Err(e) => Err(ReadError::UnitCommand(e)),
- }
- },
- 16 =>
- {
- let len = buff.read_i32()?;
- if len < 0
- {
- return Err(ReadError::BoolArrayLen(len));
- }
- let mut result = Vec::<bool>::new();
- result.reserve(len as usize);
- for _ in 0..len
- {
- result.push(buff.read_bool()?);
- }
- Ok(DynData::BoolArray(result))
- },
- 17 => Ok(DynData::Unit(buff.read_u32()?)),
- 18 =>
- {
- let len = buff.read_i16()?;
- if len < 0
- {
- return Err(ReadError::Vec2ArrayLen(len));
- }
- let mut result = Vec::<(f32, f32)>::new();
- result.reserve(len as usize);
- for _ in 0..len
- {
- result.push((buff.read_f32()?, buff.read_f32()?));
- }
- Ok(DynData::Vec2Array(result))
- },
- 19 => Ok(DynData::Vec2(buff.read_f32()?, buff.read_f32()?)),
- 20 => Ok(DynData::Team(Team::of(buff.read_u8()?))),
- id => Err(ReadError::Type(id)),
- }
- }
-
- fn serialize(&mut self, buff: &mut DataWrite<'_>, data: &DynData) -> Result<(), Self::WriteError>
- {
- match data
- {
- DynData::Empty =>
- {
- buff.write_u8(0)?;
- Ok(())
- },
- DynData::Int(val) =>
- {
- buff.write_u8(1)?;
- buff.write_i32(*val)?;
- Ok(())
- },
- DynData::Long(val) =>
- {
- buff.write_u8(2)?;
- buff.write_i64(*val)?;
- Ok(())
- },
- DynData::Float(val) =>
- {
- buff.write_u8(3)?;
- buff.write_f32(*val)?;
- Ok(())
- },
- DynData::String(opt) =>
- {
- buff.write_u8(4)?;
- match opt
- {
- None => buff.write_bool(false)?,
- Some(s) =>
- {
- buff.write_bool(true)?;
- buff.write_utf(s)?
- },
- }
- Ok(())
- },
- DynData::Content(ty, id) =>
- {
- buff.write_u8(5)?;
- buff.write_u8((*ty).into())?;
- buff.write_u16(*id)?;
- Ok(())
- },
- DynData::IntArray(arr) =>
- {
- if arr.len() > i16::MAX as usize
- {
- return Err(WriteError::IntArrayLen(arr.len()));
- }
- buff.write_u8(6)?;
- buff.write_i16(arr.len() as i16)?;
- for &v in arr.iter()
- {
- buff.write_i32(v)?;
- }
- Ok(())
- },
- DynData::Point2(x, y) =>
- {
- buff.write_u8(7)?;
- buff.write_i32(*x)?;
- buff.write_i32(*y)?;
- Ok(())
- },
- DynData::Point2Array(arr) =>
- {
- if arr.len() > i8::MAX as usize
- {
- return Err(WriteError::Point2ArrayLen(arr.len()));
- }
- buff.write_u8(8)?;
- buff.write_i8(arr.len() as i8)?;
- for &(x, y) in arr.iter()
- {
- buff.write_i32(((x as i32) << 16) | ((y as i32) & 0xFFFF))?;
- }
- Ok(())
- },
- DynData::TechNode(ty, id) =>
- {
- buff.write_u8(9)?;
- buff.write_u8((*ty).into())?;
- buff.write_u16(*id)?;
- Ok(())
- },
- DynData::Boolean(val) =>
- {
- buff.write_u8(10)?;
- buff.write_bool(*val)?;
- Ok(())
- },
- DynData::Double(val) =>
- {
- buff.write_u8(11)?;
- buff.write_f64(*val)?;
- Ok(())
- },
- DynData::Building(pos) =>
- {
- buff.write_u8(12)?;
- buff.write_u32(u32::from(*pos))?;
- Ok(())
- },
- DynData::LogicField(fld) =>
- {
- buff.write_u8(13)?;
- buff.write_u8(u8::from(*fld))?;
- Ok(())
- },
- DynData::ByteArray(arr) =>
- {
- if arr.len() > i32::MAX as usize
- {
- return Err(WriteError::ByteArrayLen(arr.len()));
- }
- buff.write_u8(14)?;
- buff.write_i32(arr.len() as i32)?;
- buff.write_bytes(arr)?;
- Ok(())
- },
- DynData::UnitCommand(cmd) =>
- {
- buff.write_u8(15)?;
- buff.write_u8(u8::from(*cmd))?;
- Ok(())
- },
- DynData::BoolArray(arr) =>
- {
- if arr.len() > i32::MAX as usize
- {
- return Err(WriteError::BoolArrayLen(arr.len()));
- }
- buff.write_u8(16)?;
- buff.write_i32(arr.len() as i32)?;
- for &b in arr.iter()
- {
- buff.write_bool(b)?;
- }
- Ok(())
- },
- DynData::Unit(id) =>
- {
- buff.write_u8(17)?;
- buff.write_u32(*id)?;
- Ok(())
- },
- DynData::Vec2Array(arr) =>
- {
- if arr.len() > i16::MAX as usize
- {
- return Err(WriteError::Vec2ArrayLen(arr.len()));
- }
- buff.write_u8(18)?;
- buff.write_i16(arr.len() as i16)?;
- for &(x, y) in arr.iter()
- {
- buff.write_f32(x)?;
- buff.write_f32(y)?;
- }
- Ok(())
- },
- DynData::Vec2(x, y) =>
- {
- buff.write_u8(19)?;
- buff.write_f32(*x)?;
- buff.write_f32(*y)?;
- Ok(())
- },
- DynData::Team(team) =>
- {
- buff.write_u8(20)?;
- buff.write_u8(u8::from(*team))?;
- Ok(())
- },
- }
- }
+impl Serializer<DynData> for DynSerializer {
+ type ReadError = ReadError;
+ type WriteError = WriteError;
+
+ fn deserialize(&mut self, buff: &mut DataRead<'_>) -> Result<DynData, Self::ReadError> {
+ match buff.read_u8()? {
+ 0 => Ok(DynData::Empty),
+ 1 => Ok(DynData::Int(buff.read_i32()?)),
+ 2 => Ok(DynData::Long(buff.read_i64()?)),
+ 3 => Ok(DynData::Float(buff.read_f32()?)),
+ 4 => {
+ if buff.read_bool()? {
+ Ok(DynData::String(Some(String::from(buff.read_utf()?))))
+ } else {
+ Ok(DynData::String(None))
+ }
+ }
+ 5 => Ok(DynData::Content(
+ content::Type::try_from(buff.read_u8()?)?,
+ buff.read_u16()?,
+ )),
+ 6 => {
+ let len = buff.read_i16()?;
+ if len < 0 {
+ return Err(ReadError::IntArrayLen(len));
+ }
+ let mut result = Vec::<i32>::new();
+ result.reserve(len as usize);
+ for _ in 0..len {
+ result.push(buff.read_i32()?);
+ }
+ Ok(DynData::IntArray(result))
+ }
+ 7 => Ok(DynData::Point2(buff.read_i32()?, buff.read_i32()?)),
+ 8 => {
+ let len = buff.read_i8()?;
+ if len < 0 {
+ return Err(ReadError::Point2ArrayLen(len));
+ }
+ let mut result = Vec::<(i16, i16)>::new();
+ result.reserve(len as usize);
+ for _ in 0..len {
+ let pt = buff.read_i32()?;
+ result.push(((pt >> 16) as i16, pt as i16));
+ }
+ Ok(DynData::Point2Array(result))
+ }
+ 9 => Ok(DynData::TechNode(
+ content::Type::try_from(buff.read_u8()?)?,
+ buff.read_u16()?,
+ )),
+ 10 => Ok(DynData::Boolean(buff.read_bool()?)),
+ 11 => Ok(DynData::Double(buff.read_f64()?)),
+ 12 => Ok(DynData::Building(GridPos::from(buff.read_u32()?))),
+ 13 => {
+ let id = buff.read_u8()?;
+ match LogicField::try_from(id) {
+ Ok(f) => Ok(DynData::LogicField(f)),
+ Err(..) => Err(ReadError::LogicField(id)),
+ }
+ }
+ 14 => {
+ let len = buff.read_i32()?;
+ if len < 0 {
+ return Err(ReadError::ByteArrayLen(len));
+ }
+ let mut result = Vec::<u8>::new();
+ buff.read_vec(&mut result, len as usize)?;
+ Ok(DynData::ByteArray(result))
+ }
+ 15 => {
+ let id = buff.read_u8()?;
+ match UnitCommand::try_from(id) {
+ Ok(f) => Ok(DynData::UnitCommand(f)),
+ Err(e) => Err(ReadError::UnitCommand(e)),
+ }
+ }
+ 16 => {
+ let len = buff.read_i32()?;
+ if len < 0 {
+ return Err(ReadError::BoolArrayLen(len));
+ }
+ let mut result = Vec::<bool>::new();
+ result.reserve(len as usize);
+ for _ in 0..len {
+ result.push(buff.read_bool()?);
+ }
+ Ok(DynData::BoolArray(result))
+ }
+ 17 => Ok(DynData::Unit(buff.read_u32()?)),
+ 18 => {
+ let len = buff.read_i16()?;
+ if len < 0 {
+ return Err(ReadError::Vec2ArrayLen(len));
+ }
+ let mut result = Vec::<(f32, f32)>::new();
+ result.reserve(len as usize);
+ for _ in 0..len {
+ result.push((buff.read_f32()?, buff.read_f32()?));
+ }
+ Ok(DynData::Vec2Array(result))
+ }
+ 19 => Ok(DynData::Vec2(buff.read_f32()?, buff.read_f32()?)),
+ 20 => Ok(DynData::Team(Team::of(buff.read_u8()?))),
+ id => Err(ReadError::Type(id)),
+ }
+ }
+
+ fn serialize(
+ &mut self,
+ buff: &mut DataWrite<'_>,
+ data: &DynData,
+ ) -> Result<(), Self::WriteError> {
+ match data {
+ DynData::Empty => {
+ buff.write_u8(0)?;
+ Ok(())
+ }
+ DynData::Int(val) => {
+ buff.write_u8(1)?;
+ buff.write_i32(*val)?;
+ Ok(())
+ }
+ DynData::Long(val) => {
+ buff.write_u8(2)?;
+ buff.write_i64(*val)?;
+ Ok(())
+ }
+ DynData::Float(val) => {
+ buff.write_u8(3)?;
+ buff.write_f32(*val)?;
+ Ok(())
+ }
+ DynData::String(opt) => {
+ buff.write_u8(4)?;
+ match opt {
+ None => buff.write_bool(false)?,
+ Some(s) => {
+ buff.write_bool(true)?;
+ buff.write_utf(s)?
+ }
+ }
+ Ok(())
+ }
+ DynData::Content(ty, id) => {
+ buff.write_u8(5)?;
+ buff.write_u8((*ty).into())?;
+ buff.write_u16(*id)?;
+ Ok(())
+ }
+ DynData::IntArray(arr) => {
+ if arr.len() > i16::MAX as usize {
+ return Err(WriteError::IntArrayLen(arr.len()));
+ }
+ buff.write_u8(6)?;
+ buff.write_i16(arr.len() as i16)?;
+ for &v in arr.iter() {
+ buff.write_i32(v)?;
+ }
+ Ok(())
+ }
+ DynData::Point2(x, y) => {
+ buff.write_u8(7)?;
+ buff.write_i32(*x)?;
+ buff.write_i32(*y)?;
+ Ok(())
+ }
+ DynData::Point2Array(arr) => {
+ if arr.len() > i8::MAX as usize {
+ return Err(WriteError::Point2ArrayLen(arr.len()));
+ }
+ buff.write_u8(8)?;
+ buff.write_i8(arr.len() as i8)?;
+ for &(x, y) in arr.iter() {
+ buff.write_i32(((x as i32) << 16) | ((y as i32) & 0xFFFF))?;
+ }
+ Ok(())
+ }
+ DynData::TechNode(ty, id) => {
+ buff.write_u8(9)?;
+ buff.write_u8((*ty).into())?;
+ buff.write_u16(*id)?;
+ Ok(())
+ }
+ DynData::Boolean(val) => {
+ buff.write_u8(10)?;
+ buff.write_bool(*val)?;
+ Ok(())
+ }
+ DynData::Double(val) => {
+ buff.write_u8(11)?;
+ buff.write_f64(*val)?;
+ Ok(())
+ }
+ DynData::Building(pos) => {
+ buff.write_u8(12)?;
+ buff.write_u32(u32::from(*pos))?;
+ Ok(())
+ }
+ DynData::LogicField(fld) => {
+ buff.write_u8(13)?;
+ buff.write_u8(u8::from(*fld))?;
+ Ok(())
+ }
+ DynData::ByteArray(arr) => {
+ if arr.len() > i32::MAX as usize {
+ return Err(WriteError::ByteArrayLen(arr.len()));
+ }
+ buff.write_u8(14)?;
+ buff.write_i32(arr.len() as i32)?;
+ buff.write_bytes(arr)?;
+ Ok(())
+ }
+ DynData::UnitCommand(cmd) => {
+ buff.write_u8(15)?;
+ buff.write_u8(u8::from(*cmd))?;
+ Ok(())
+ }
+ DynData::BoolArray(arr) => {
+ if arr.len() > i32::MAX as usize {
+ return Err(WriteError::BoolArrayLen(arr.len()));
+ }
+ buff.write_u8(16)?;
+ buff.write_i32(arr.len() as i32)?;
+ for &b in arr.iter() {
+ buff.write_bool(b)?;
+ }
+ Ok(())
+ }
+ DynData::Unit(id) => {
+ buff.write_u8(17)?;
+ buff.write_u32(*id)?;
+ Ok(())
+ }
+ DynData::Vec2Array(arr) => {
+ if arr.len() > i16::MAX as usize {
+ return Err(WriteError::Vec2ArrayLen(arr.len()));
+ }
+ buff.write_u8(18)?;
+ buff.write_i16(arr.len() as i16)?;
+ for &(x, y) in arr.iter() {
+ buff.write_f32(x)?;
+ buff.write_f32(y)?;
+ }
+ Ok(())
+ }
+ DynData::Vec2(x, y) => {
+ buff.write_u8(19)?;
+ buff.write_f32(*x)?;
+ buff.write_f32(*y)?;
+ Ok(())
+ }
+ DynData::Team(team) => {
+ buff.write_u8(20)?;
+ buff.write_u8(u8::from(*team))?;
+ Ok(())
+ }
+ }
+ }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum ReadError
-{
- Underlying(data::ReadError),
- Type(u8),
- ContentType(content::TryFromU8Error),
- IntArrayLen(i16),
- Point2ArrayLen(i8),
- LogicField(u8),
- ByteArrayLen(i32),
- UnitCommand(command::TryFromU8Error),
- BoolArrayLen(i32),
- Vec2ArrayLen(i16),
+pub enum ReadError {
+ Underlying(data::ReadError),
+ Type(u8),
+ ContentType(content::TryFromU8Error),
+ IntArrayLen(i16),
+ Point2ArrayLen(i8),
+ LogicField(u8),
+ ByteArrayLen(i32),
+ UnitCommand(command::TryFromU8Error),
+ BoolArrayLen(i32),
+ Vec2ArrayLen(i16),
}
-impl From<data::ReadError> for ReadError
-{
- fn from(err: data::ReadError) -> Self
- {
- Self::Underlying(err)
- }
+impl From<data::ReadError> for ReadError {
+ fn from(err: data::ReadError) -> Self {
+ Self::Underlying(err)
+ }
}
-impl From<content::TryFromU8Error> for ReadError
-{
- fn from(err: content::TryFromU8Error) -> Self
- {
- Self::ContentType(err)
- }
+impl From<content::TryFromU8Error> for ReadError {
+ fn from(err: content::TryFromU8Error) -> Self {
+ Self::ContentType(err)
+ }
}
-impl From<command::TryFromU8Error> for ReadError
-{
- fn from(err: command::TryFromU8Error) -> Self
- {
- Self::UnitCommand(err)
- }
+impl From<command::TryFromU8Error> for ReadError {
+ fn from(err: command::TryFromU8Error) -> Self {
+ Self::UnitCommand(err)
+ }
}
-impl fmt::Display for ReadError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Underlying(..) => f.write_str("failed to read from buffer"),
- Self::Type(id) => write!(f, "invalid dynamic data type ({id})"),
- Self::ContentType(..) => f.write_str("content type not found"),
- Self::IntArrayLen(len) => write!(f, "integer array too long ({len})"),
- Self::Point2ArrayLen(len) => write!(f, "point2 array too long ({len})"),
- Self::LogicField(id) => write!(f, "invalid logic field ({id})"),
- Self::ByteArrayLen(len) => write!(f, "byte array too long ({len})"),
- Self::UnitCommand(..) => f.write_str("unit command not found"),
- Self::BoolArrayLen(len) => write!(f, "boolean array too long ({len})"),
- Self::Vec2ArrayLen(len) => write!(f, "vec2 array too long ({len})"),
- }
- }
+impl fmt::Display for ReadError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Underlying(..) => f.write_str("failed to read from buffer"),
+ Self::Type(id) => write!(f, "invalid dynamic data type ({id})"),
+ Self::ContentType(..) => f.write_str("content type not found"),
+ Self::IntArrayLen(len) => write!(f, "integer array too long ({len})"),
+ Self::Point2ArrayLen(len) => write!(f, "point2 array too long ({len})"),
+ Self::LogicField(id) => write!(f, "invalid logic field ({id})"),
+ Self::ByteArrayLen(len) => write!(f, "byte array too long ({len})"),
+ Self::UnitCommand(..) => f.write_str("unit command not found"),
+ Self::BoolArrayLen(len) => write!(f, "boolean array too long ({len})"),
+ Self::Vec2ArrayLen(len) => write!(f, "vec2 array too long ({len})"),
+ }
+ }
}
-impl Error for ReadError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::Underlying(e) => Some(e),
- _ => None,
- }
- }
+impl Error for ReadError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Underlying(e) => Some(e),
+ _ => None,
+ }
+ }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum WriteError
-{
- Underlying(data::WriteError),
- IntArrayLen(usize),
- Point2ArrayLen(usize),
- ByteArrayLen(usize),
- BoolArrayLen(usize),
- Vec2ArrayLen(usize),
+pub enum WriteError {
+ Underlying(data::WriteError),
+ IntArrayLen(usize),
+ Point2ArrayLen(usize),
+ ByteArrayLen(usize),
+ BoolArrayLen(usize),
+ Vec2ArrayLen(usize),
}
-impl From<data::WriteError> for WriteError
-{
- fn from(err: data::WriteError) -> Self
- {
- Self::Underlying(err)
- }
+impl From<data::WriteError> for WriteError {
+ fn from(err: data::WriteError) -> Self {
+ Self::Underlying(err)
+ }
}
-impl fmt::Display for WriteError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Underlying(..) => f.write_str("failed to write to buffer"),
- Self::IntArrayLen(len) => write!(f, "integer array too long ({len})"),
- Self::Point2ArrayLen(len) => write!(f, "point2 array too long ({len})"),
- Self::ByteArrayLen(len) => write!(f, "byte array too long ({len})"),
- Self::BoolArrayLen(len) => write!(f, "boolean array too long ({len})"),
- Self::Vec2ArrayLen(len) => write!(f, "vec2 array too long ({len})"),
- }
- }
+impl fmt::Display for WriteError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Underlying(..) => f.write_str("failed to write to buffer"),
+ Self::IntArrayLen(len) => write!(f, "integer array too long ({len})"),
+ Self::Point2ArrayLen(len) => write!(f, "point2 array too long ({len})"),
+ Self::ByteArrayLen(len) => write!(f, "byte array too long ({len})"),
+ Self::BoolArrayLen(len) => write!(f, "boolean array too long ({len})"),
+ Self::Vec2ArrayLen(len) => write!(f, "vec2 array too long ({len})"),
+ }
+ }
}
-impl Error for WriteError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::Underlying(e) => Some(e),
- _ => None,
- }
- }
+impl Error for WriteError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Underlying(e) => Some(e),
+ _ => None,
+ }
+ }
}
#[cfg(test)]
-mod test
-{
- use super::*;
- use crate::team::{CRUX, DERELICT, SHARDED};
-
- macro_rules!_zero
- {
- ($tt:tt) => {0usize};
- }
-
- macro_rules!make_dyn_test
+mod test {
+ use super::*;
+ use crate::team::{CRUX, DERELICT, SHARDED};
+
+ macro_rules! _zero {
+ ($tt:tt) => {
+ 0usize
+ };
+ }
+
+ macro_rules!make_dyn_test
{
($name:ident, $($val:expr),+) =>
{
@@ -541,26 +487,117 @@ mod test
}
};
}
-
- make_dyn_test!(reparse_empty, DynData::Empty, DynData::Empty, DynData::Empty);
- make_dyn_test!(reparse_int, DynData::Int(581923), DynData::Int(2147483647), DynData::Int(-1047563850));
- make_dyn_test!(reparse_long, DynData::Long(11295882949812), DynData::Long(-5222358074010407789));
- make_dyn_test!(reparse_float, DynData::Float(3.14159265), DynData::Float(f32::INFINITY), DynData::Float(f32::EPSILON), DynData::Float(f32::NAN));
- make_dyn_test!(reparse_string, DynData::String(None), DynData::String(Some("hello \u{10FE03}".to_string())), DynData::String(Some("".to_string())));
- make_dyn_test!(reparse_content, DynData::Content(content::Type::Item, 12345), DynData::Content(content::Type::Planet, 25431));
- make_dyn_test!(reparse_int_array, DynData::IntArray(vec![581923, 2147483647, -1047563850]), DynData::IntArray(vec![1902864703]));
- make_dyn_test!(reparse_point2, DynData::Point2(17, 0), DynData::Point2(234, -345), DynData::Point2(-2147483648, -1));
- make_dyn_test!(reparse_point2_array, DynData::Point2Array(vec![(44, 55), (-33, 66), (-22, -77)]), DynData::Point2Array(vec![(22, -88)]));
- make_dyn_test!(reparse_technode, DynData::TechNode(content::Type::Item, 12345), DynData::TechNode(content::Type::Planet, 25431));
- make_dyn_test!(reparse_boolean, DynData::Boolean(false), DynData::Boolean(true), DynData::Boolean(false));
- make_dyn_test!(reparse_double, DynData::Double(2.718281828459045), DynData::Double(-f64::INFINITY), DynData::Double(f64::NAN));
- make_dyn_test!(reparse_building, DynData::Building(GridPos(10, 0)), DynData::Building(GridPos(4444, 0xFE98)));
- make_dyn_test!(reparse_logic, DynData::LogicField(LogicField::Enabled), DynData::LogicField(LogicField::Shoot), DynData::LogicField(LogicField::Color));
- make_dyn_test!(reparse_byte_array, DynData::ByteArray(b"c\x00nstruct \xADditio\nal pylons".to_vec()), DynData::ByteArray(b"\x00\x01\xFE\xFF".to_vec()));
- make_dyn_test!(reparse_unit_command, DynData::UnitCommand(UnitCommand::Idle), DynData::UnitCommand(UnitCommand::Rally));
- make_dyn_test!(reparse_bool_array, DynData::BoolArray(vec![true, true, true, false, true, false, true]), DynData::BoolArray(vec![false, true]));
- make_dyn_test!(reparse_unit, DynData::Unit(0), DynData::Unit(2147483647));
- make_dyn_test!(reparse_vec2_array, DynData::Vec2Array(vec![(4.4, 5.5), (-3.3, 6.6), (-2.2, -7.7)]), DynData::Vec2Array(vec![(2.2, -8.8)]));
- make_dyn_test!(reparse_vec2, DynData::Vec2(1.5, 9.1234), DynData::Vec2(-0.0, -17.0), DynData::Vec2(-10.7, 3.8));
- make_dyn_test!(reparse_team, DynData::Team(SHARDED), DynData::Team(CRUX), DynData::Team(DERELICT));
+
+ make_dyn_test!(
+ reparse_empty,
+ DynData::Empty,
+ DynData::Empty,
+ DynData::Empty
+ );
+ make_dyn_test!(
+ reparse_int,
+ DynData::Int(581923),
+ DynData::Int(2147483647),
+ DynData::Int(-1047563850)
+ );
+ make_dyn_test!(
+ reparse_long,
+ DynData::Long(11295882949812),
+ DynData::Long(-5222358074010407789)
+ );
+ make_dyn_test!(
+ reparse_float,
+ DynData::Float(3.14159265),
+ DynData::Float(f32::INFINITY),
+ DynData::Float(f32::EPSILON),
+ DynData::Float(f32::NAN)
+ );
+ make_dyn_test!(
+ reparse_string,
+ DynData::String(None),
+ DynData::String(Some("hello \u{10FE03}".to_string())),
+ DynData::String(Some("".to_string()))
+ );
+ make_dyn_test!(
+ reparse_content,
+ DynData::Content(content::Type::Item, 12345),
+ DynData::Content(content::Type::Planet, 25431)
+ );
+ make_dyn_test!(
+ reparse_int_array,
+ DynData::IntArray(vec![581923, 2147483647, -1047563850]),
+ DynData::IntArray(vec![1902864703])
+ );
+ make_dyn_test!(
+ reparse_point2,
+ DynData::Point2(17, 0),
+ DynData::Point2(234, -345),
+ DynData::Point2(-2147483648, -1)
+ );
+ make_dyn_test!(
+ reparse_point2_array,
+ DynData::Point2Array(vec![(44, 55), (-33, 66), (-22, -77)]),
+ DynData::Point2Array(vec![(22, -88)])
+ );
+ make_dyn_test!(
+ reparse_technode,
+ DynData::TechNode(content::Type::Item, 12345),
+ DynData::TechNode(content::Type::Planet, 25431)
+ );
+ make_dyn_test!(
+ reparse_boolean,
+ DynData::Boolean(false),
+ DynData::Boolean(true),
+ DynData::Boolean(false)
+ );
+ make_dyn_test!(
+ reparse_double,
+ DynData::Double(2.718281828459045),
+ DynData::Double(-f64::INFINITY),
+ DynData::Double(f64::NAN)
+ );
+ make_dyn_test!(
+ reparse_building,
+ DynData::Building(GridPos(10, 0)),
+ DynData::Building(GridPos(4444, 0xFE98))
+ );
+ make_dyn_test!(
+ reparse_logic,
+ DynData::LogicField(LogicField::Enabled),
+ DynData::LogicField(LogicField::Shoot),
+ DynData::LogicField(LogicField::Color)
+ );
+ make_dyn_test!(
+ reparse_byte_array,
+ DynData::ByteArray(b"c\x00nstruct \xADditio\nal pylons".to_vec()),
+ DynData::ByteArray(b"\x00\x01\xFE\xFF".to_vec())
+ );
+ make_dyn_test!(
+ reparse_unit_command,
+ DynData::UnitCommand(UnitCommand::Idle),
+ DynData::UnitCommand(UnitCommand::Rally)
+ );
+ make_dyn_test!(
+ reparse_bool_array,
+ DynData::BoolArray(vec![true, true, true, false, true, false, true]),
+ DynData::BoolArray(vec![false, true])
+ );
+ make_dyn_test!(reparse_unit, DynData::Unit(0), DynData::Unit(2147483647));
+ make_dyn_test!(
+ reparse_vec2_array,
+ DynData::Vec2Array(vec![(4.4, 5.5), (-3.3, 6.6), (-2.2, -7.7)]),
+ DynData::Vec2Array(vec![(2.2, -8.8)])
+ );
+ make_dyn_test!(
+ reparse_vec2,
+ DynData::Vec2(1.5, 9.1234),
+ DynData::Vec2(-0.0, -17.0),
+ DynData::Vec2(-10.7, 3.8)
+ );
+ make_dyn_test!(
+ reparse_team,
+ DynData::Team(SHARDED),
+ DynData::Team(CRUX),
+ DynData::Team(DERELICT)
+ );
}
diff --git a/src/data/mod.rs b/src/data/mod.rs
index b8ab386..cf1b19f 100644
--- a/src/data/mod.rs
+++ b/src/data/mod.rs
@@ -7,365 +7,331 @@ pub mod command;
pub mod dynamic;
pub mod schematic;
-pub struct DataRead<'d>
-{
- data: &'d [u8]
+pub struct DataRead<'d> {
+ data: &'d [u8],
}
-macro_rules!make_read
-{
- ($name:ident, $type:ty) =>
- {
- pub fn $name(&mut self) -> Result<$type, ReadError>
- {
- const LEN: usize = std::mem::size_of::<$type>();
- if self.data.len() < LEN
- {
- return Err(ReadError::Underflow{need: LEN, have: self.data.len()});
- }
- let mut output = [0u8; LEN];
- output.copy_from_slice(&self.data[..LEN]);
- self.data = &self.data[LEN..];
- Ok(<$type>::from_be_bytes(output))
- }
- };
+macro_rules! make_read {
+ ($name:ident, $type:ty) => {
+ pub fn $name(&mut self) -> Result<$type, ReadError> {
+ const LEN: usize = std::mem::size_of::<$type>();
+ if self.data.len() < LEN {
+ return Err(ReadError::Underflow {
+ need: LEN,
+ have: self.data.len(),
+ });
+ }
+ let mut output = [0u8; LEN];
+ output.copy_from_slice(&self.data[..LEN]);
+ self.data = &self.data[LEN..];
+ Ok(<$type>::from_be_bytes(output))
+ }
+ };
}
-impl<'d> DataRead<'d>
-{
- pub fn new(data: &'d [u8]) -> Self
- {
- Self{data}
- }
-
- pub fn read_bool(&mut self) -> Result<bool, ReadError>
- {
- Ok(self.read_u8()? != 0)
- }
-
- make_read!(read_u8, u8);
- make_read!(read_i8, i8);
- make_read!(read_u16, u16);
- make_read!(read_i16, i16);
- make_read!(read_u32, u32);
- make_read!(read_i32, i32);
- make_read!(read_f32, f32);
- make_read!(read_u64, u64);
- make_read!(read_i64, i64);
- make_read!(read_f64, f64);
-
- pub fn read_utf(&mut self) -> Result<&'d str, ReadError>
- {
- if self.data.len() < 2
- {
- return Err(ReadError::Underflow{need: 2, have: self.data.len()});
- }
- let len = u16::from_be_bytes([self.data[0], self.data[1]]);
- let end = 2 + len as usize;
- if self.data.len() < end
- {
- return Err(ReadError::Underflow{need: end, have: self.data.len()});
- }
- let result = std::str::from_utf8(&self.data[2..end])?;
- self.data = &self.data[end..];
- Ok(result)
- }
-
- pub fn read_bytes(&mut self, dst: &mut [u8]) -> Result<(), ReadError>
- {
- if self.data.len() < dst.len()
- {
- return Err(ReadError::Underflow{need: dst.len(), have: self.data.len()});
- }
- dst.copy_from_slice(&self.data[..dst.len()]);
- self.data = &self.data[dst.len()..];
- Ok(())
- }
-
- pub fn read_vec(&mut self, dst: &mut Vec<u8>, len: usize) -> Result<(), ReadError>
- {
- if self.data.len() < len
- {
- return Err(ReadError::Underflow{need: len, have: self.data.len()});
- }
- dst.extend_from_slice(&self.data[..len]);
- self.data = &self.data[len..];
- Ok(())
- }
+impl<'d> DataRead<'d> {
+ pub fn new(data: &'d [u8]) -> Self {
+ Self { data }
+ }
+
+ pub fn read_bool(&mut self) -> Result<bool, ReadError> {
+ Ok(self.read_u8()? != 0)
+ }
+
+ make_read!(read_u8, u8);
+ make_read!(read_i8, i8);
+ make_read!(read_u16, u16);
+ make_read!(read_i16, i16);
+ make_read!(read_u32, u32);
+ make_read!(read_i32, i32);
+ make_read!(read_f32, f32);
+ make_read!(read_u64, u64);
+ make_read!(read_i64, i64);
+ make_read!(read_f64, f64);
+
+ pub fn read_utf(&mut self) -> Result<&'d str, ReadError> {
+ if self.data.len() < 2 {
+ return Err(ReadError::Underflow {
+ need: 2,
+ have: self.data.len(),
+ });
+ }
+ let len = u16::from_be_bytes([self.data[0], self.data[1]]);
+ let end = 2 + len as usize;
+ if self.data.len() < end {
+ return Err(ReadError::Underflow {
+ need: end,
+ have: self.data.len(),
+ });
+ }
+ let result = std::str::from_utf8(&self.data[2..end])?;
+ self.data = &self.data[end..];
+ Ok(result)
+ }
+
+ pub fn read_bytes(&mut self, dst: &mut [u8]) -> Result<(), ReadError> {
+ if self.data.len() < dst.len() {
+ return Err(ReadError::Underflow {
+ need: dst.len(),
+ have: self.data.len(),
+ });
+ }
+ dst.copy_from_slice(&self.data[..dst.len()]);
+ self.data = &self.data[dst.len()..];
+ Ok(())
+ }
+
+ pub fn read_vec(&mut self, dst: &mut Vec<u8>, len: usize) -> Result<(), ReadError> {
+ if self.data.len() < len {
+ return Err(ReadError::Underflow {
+ need: len,
+ have: self.data.len(),
+ });
+ }
+ dst.extend_from_slice(&self.data[..len]);
+ self.data = &self.data[len..];
+ Ok(())
+ }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum ReadError
-{
- Underflow{need: usize, have: usize},
- Utf8(Utf8Error),
+pub enum ReadError {
+ Underflow { need: usize, have: usize },
+ Utf8(Utf8Error),
}
-impl From<Utf8Error> for ReadError
-{
- fn from(err: Utf8Error) -> Self
- {
- Self::Utf8(err)
- }
+impl From<Utf8Error> for ReadError {
+ fn from(err: Utf8Error) -> Self {
+ Self::Utf8(err)
+ }
}
-impl fmt::Display for ReadError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Underflow{need, have} => write!(f, "buffer underflow (expected {need} but got {have})"),
- Self::Utf8(..) => f.write_str("malformed utf-8 in string"),
- }
- }
+impl fmt::Display for ReadError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Underflow { need, have } => {
+ write!(f, "buffer underflow (expected {need} but got {have})")
+ }
+ Self::Utf8(..) => f.write_str("malformed utf-8 in string"),
+ }
+ }
}
-impl Error for ReadError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::Utf8(e) => Some(e),
- _ => None,
- }
- }
+impl Error for ReadError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Utf8(e) => Some(e),
+ _ => None,
+ }
+ }
}
-enum WriteBuff<'d>
-{
- // unlike the DataRead want to access the written region after
- Ref{raw: &'d mut [u8], pos: usize},
- Vec(Vec<u8>),
+enum WriteBuff<'d> {
+ // unlike the DataRead want to access the written region after
+ Ref { raw: &'d mut [u8], pos: usize },
+ Vec(Vec<u8>),
}
-impl<'d> WriteBuff<'d>
-{
- fn check_capacity(&self, need: usize) -> Result<(), WriteError>
- {
- match self
- {
- Self::Ref{raw, pos} if raw.len() - pos < need => Err(WriteError::Overflow{need, have: raw.len() - pos}),
- _ => Ok(()),
- }
- }
-
- fn write(&mut self, data: &[u8])
- {
- match self
- {
- Self::Ref{raw, pos} =>
- {
- let end = *pos + data.len();
- raw[*pos..end].copy_from_slice(data);
- *pos += data.len();
- },
- Self::Vec(v) => v.extend_from_slice(data),
- }
- }
+impl<'d> WriteBuff<'d> {
+ fn check_capacity(&self, need: usize) -> Result<(), WriteError> {
+ match self {
+ Self::Ref { raw, pos } if raw.len() - pos < need => Err(WriteError::Overflow {
+ need,
+ have: raw.len() - pos,
+ }),
+ _ => Ok(()),
+ }
+ }
+
+ fn write(&mut self, data: &[u8]) {
+ match self {
+ Self::Ref { raw, pos } => {
+ let end = *pos + data.len();
+ raw[*pos..end].copy_from_slice(data);
+ *pos += data.len();
+ }
+ Self::Vec(v) => v.extend_from_slice(data),
+ }
+ }
}
-pub struct DataWrite<'d>
-{
- data: WriteBuff<'d>
+pub struct DataWrite<'d> {
+ data: WriteBuff<'d>,
}
-macro_rules!make_write
-{
- ($name:ident, $type:ty) =>
- {
- pub fn $name(&mut self, val: $type) -> Result<(), WriteError>
- {
- const LEN: usize = std::mem::size_of::<$type>();
- self.data.check_capacity(LEN)?;
- self.data.write(&<$type>::to_be_bytes(val));
- Ok(())
- }
- };
+macro_rules! make_write {
+ ($name:ident, $type:ty) => {
+ pub fn $name(&mut self, val: $type) -> Result<(), WriteError> {
+ const LEN: usize = std::mem::size_of::<$type>();
+ self.data.check_capacity(LEN)?;
+ self.data.write(&<$type>::to_be_bytes(val));
+ Ok(())
+ }
+ };
}
-impl<'d> DataWrite<'d>
-{
- pub fn write_bool(&mut self, val: bool) -> Result<(), WriteError>
- {
- self.write_u8(val as u8)
- }
-
- make_write!(write_u8, u8);
- make_write!(write_i8, i8);
- make_write!(write_u16, u16);
- make_write!(write_i16, i16);
- make_write!(write_u32, u32);
- make_write!(write_i32, i32);
- make_write!(write_f32, f32);
- make_write!(write_u64, u64);
- make_write!(write_i64, i64);
- make_write!(write_f64, f64);
-
- pub fn write_utf(&mut self, val: &str) -> Result<(), WriteError>
- {
- if val.len() > u16::MAX as usize
- {
- return Err(WriteError::TooLong{len: val.len()});
- }
- self.data.check_capacity(2 + val.len())?;
- self.data.write(&u16::to_be_bytes(val.len() as u16));
- self.data.write(val.as_bytes());
- Ok(())
- }
-
- pub fn write_bytes(&mut self, val: &[u8]) -> Result<(), WriteError>
- {
- self.data.check_capacity(val.len())?;
- self.data.write(val);
- Ok(())
- }
-
- pub fn is_owned(&self) -> bool
- {
- match self.data
- {
- WriteBuff::Vec(..) => true,
- _ => false,
- }
- }
-
- pub fn get_written(&self) -> &[u8]
- {
- match &self.data
- {
- WriteBuff::Ref{raw, pos} => &raw[..*pos],
- WriteBuff::Vec(v) => &v,
- }
- }
+impl<'d> DataWrite<'d> {
+ pub fn write_bool(&mut self, val: bool) -> Result<(), WriteError> {
+ self.write_u8(val as u8)
+ }
+
+ make_write!(write_u8, u8);
+ make_write!(write_i8, i8);
+ make_write!(write_u16, u16);
+ make_write!(write_i16, i16);
+ make_write!(write_u32, u32);
+ make_write!(write_i32, i32);
+ make_write!(write_f32, f32);
+ make_write!(write_u64, u64);
+ make_write!(write_i64, i64);
+ make_write!(write_f64, f64);
+
+ pub fn write_utf(&mut self, val: &str) -> Result<(), WriteError> {
+ if val.len() > u16::MAX as usize {
+ return Err(WriteError::TooLong { len: val.len() });
+ }
+ self.data.check_capacity(2 + val.len())?;
+ self.data.write(&u16::to_be_bytes(val.len() as u16));
+ self.data.write(val.as_bytes());
+ Ok(())
+ }
+
+ pub fn write_bytes(&mut self, val: &[u8]) -> Result<(), WriteError> {
+ self.data.check_capacity(val.len())?;
+ self.data.write(val);
+ Ok(())
+ }
+
+ pub fn is_owned(&self) -> bool {
+ match self.data {
+ WriteBuff::Vec(..) => true,
+ _ => false,
+ }
+ }
+
+ pub fn get_written(&self) -> &[u8] {
+ match &self.data {
+ WriteBuff::Ref { raw, pos } => &raw[..*pos],
+ WriteBuff::Vec(v) => &v,
+ }
+ }
}
-impl DataWrite<'static>
-{
- pub fn new() -> Self
- {
- Self{data: WriteBuff::Vec(Vec::new())}
- }
+impl DataWrite<'static> {
+ pub fn new() -> Self {
+ Self {
+ data: WriteBuff::Vec(Vec::new()),
+ }
+ }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum WriteError
-{
- Overflow{need: usize, have: usize},
- TooLong{len: usize},
+pub enum WriteError {
+ Overflow { need: usize, have: usize },
+ TooLong { len: usize },
}
-impl fmt::Display for WriteError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Overflow{need, have} => write!(f, "buffer overflow (expected {need} but got {have})"),
- Self::TooLong{len} => write!(f, "string too long ({len} bytes of {})", u16::MAX),
- }
- }
+impl fmt::Display for WriteError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Overflow { need, have } => {
+ write!(f, "buffer overflow (expected {need} but got {have})")
+ }
+ Self::TooLong { len } => write!(f, "string too long ({len} bytes of {})", u16::MAX),
+ }
+ }
}
impl Error for WriteError {}
-impl<'d> From<&'d mut [u8]> for DataWrite<'d>
-{
- fn from(value: &'d mut [u8]) -> Self
- {
- Self{data: WriteBuff::Ref{raw: value, pos: 0}}
- }
+impl<'d> From<&'d mut [u8]> for DataWrite<'d> {
+ fn from(value: &'d mut [u8]) -> Self {
+ Self {
+ data: WriteBuff::Ref { raw: value, pos: 0 },
+ }
+ }
}
-impl From<Vec<u8>> for DataWrite<'static>
-{
- fn from(value: Vec<u8>) -> Self
- {
- Self{data: WriteBuff::Vec(value)}
- }
+impl From<Vec<u8>> for DataWrite<'static> {
+ fn from(value: Vec<u8>) -> Self {
+ Self {
+ data: WriteBuff::Vec(value),
+ }
+ }
}
-impl<'d> TryFrom<DataWrite<'d>> for Vec<u8>
-{
- type Error = ();
-
- fn try_from(value: DataWrite<'d>) -> Result<Self, Self::Error>
- {
- match value.data
- {
- WriteBuff::Vec(v) => Ok(v),
- _ => Err(()),
- }
- }
+impl<'d> TryFrom<DataWrite<'d>> for Vec<u8> {
+ type Error = ();
+
+ fn try_from(value: DataWrite<'d>) -> Result<Self, Self::Error> {
+ match value.data {
+ WriteBuff::Vec(v) => Ok(v),
+ _ => Err(()),
+ }
+ }
}
-pub trait Serializer<D>
-{
- type ReadError;
- type WriteError;
-
- fn deserialize(&mut self, buff: &mut DataRead<'_>) -> Result<D, Self::ReadError>;
-
- fn serialize(&mut self, buff: &mut DataWrite<'_>, data: &D) -> Result<(), Self::WriteError>;
+pub trait Serializer<D> {
+ type ReadError;
+ type WriteError;
+
+ fn deserialize(&mut self, buff: &mut DataRead<'_>) -> Result<D, Self::ReadError>;
+
+ fn serialize(&mut self, buff: &mut DataWrite<'_>, data: &D) -> Result<(), Self::WriteError>;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct GridPos(pub u16, pub u16);
-impl From<u32> for GridPos
-{
- fn from(value: u32) -> Self
- {
- GridPos((value >> 16) as u16, value as u16)
- }
+impl From<u32> for GridPos {
+ fn from(value: u32) -> Self {
+ GridPos((value >> 16) as u16, value as u16)
+ }
}
-impl From<GridPos> for u32
-{
- fn from(value: GridPos) -> Self
- {
- ((value.0 as u32) << 16) | (value.1 as u32)
- }
+impl From<GridPos> for u32 {
+ fn from(value: GridPos) -> Self {
+ ((value.0 as u32) << 16) | (value.1 as u32)
+ }
}
#[cfg(test)]
-mod test
-{
- use super::*;
-
- #[test]
- fn read()
- {
- let mut read = DataRead::new("Thé qûick ઉrown fox 🦘 over\0\rthe lazy dog.".as_bytes());
- assert_eq!(read.read_u8(), Ok(84));
- assert_eq!(read.read_i8(), Ok(104));
- assert_eq!(read.read_i8(), Ok(-61));
- assert_eq!(read.read_u16(), Ok(43296));
- assert_eq!(read.read_i16(), Ok(29123));
- assert_eq!(read.read_i16(), Ok(-17559));
- assert_eq!(read.read_i32(), Ok(1667965152));
- assert_eq!(read.read_i32(), Ok(-1433832849));
- assert_eq!(read.read_i64(), Ok(8605851562280493296));
- assert_eq!(read.read_i64(), Ok(-6942694510468635278));
- assert_eq!(read.read_utf(), Ok("the lazy dog."));
- }
-
- #[test]
- fn write()
- {
- let mut write = DataWrite::new();
- assert_eq!(write.write_u8(84), Ok(()));
- assert_eq!(write.write_i8(104), Ok(()));
- assert_eq!(write.write_i8(-61), Ok(()));
- assert_eq!(write.write_u16(43296), Ok(()));
- assert_eq!(write.write_i16(29123), Ok(()));
- assert_eq!(write.write_i16(-17559), Ok(()));
- assert_eq!(write.write_i32(1667965152), Ok(()));
- assert_eq!(write.write_i32(-1433832849), Ok(()));
- assert_eq!(write.write_i64(8605851562280493296), Ok(()));
- assert_eq!(write.write_i64(-6942694510468635278), Ok(()));
- assert_eq!(write.write_utf("the lazy dog."), Ok(()));
- assert_eq!(write.get_written(), "Thé qûick ઉrown fox 🦘 over\0\rthe lazy dog.".as_bytes());
- }
+mod test {
+ use super::*;
+
+ #[test]
+ fn read() {
+ let mut read = DataRead::new("Thé qûick ઉrown fox 🦘 over\0\rthe lazy dog.".as_bytes());
+ assert_eq!(read.read_u8(), Ok(84));
+ assert_eq!(read.read_i8(), Ok(104));
+ assert_eq!(read.read_i8(), Ok(-61));
+ assert_eq!(read.read_u16(), Ok(43296));
+ assert_eq!(read.read_i16(), Ok(29123));
+ assert_eq!(read.read_i16(), Ok(-17559));
+ assert_eq!(read.read_i32(), Ok(1667965152));
+ assert_eq!(read.read_i32(), Ok(-1433832849));
+ assert_eq!(read.read_i64(), Ok(8605851562280493296));
+ assert_eq!(read.read_i64(), Ok(-6942694510468635278));
+ assert_eq!(read.read_utf(), Ok("the lazy dog."));
+ }
+
+ #[test]
+ fn write() {
+ let mut write = DataWrite::new();
+ assert_eq!(write.write_u8(84), Ok(()));
+ assert_eq!(write.write_i8(104), Ok(()));
+ assert_eq!(write.write_i8(-61), Ok(()));
+ assert_eq!(write.write_u16(43296), Ok(()));
+ assert_eq!(write.write_i16(29123), Ok(()));
+ assert_eq!(write.write_i16(-17559), Ok(()));
+ assert_eq!(write.write_i32(1667965152), Ok(()));
+ assert_eq!(write.write_i32(-1433832849), Ok(()));
+ assert_eq!(write.write_i64(8605851562280493296), Ok(()));
+ assert_eq!(write.write_i64(-6942694510468635278), Ok(()));
+ assert_eq!(write.write_utf("the lazy dog."), Ok(()));
+ assert_eq!(
+ write.get_written(),
+ "Thé qûick ઉrown fox 🦘 over\0\rthe lazy dog.".as_bytes()
+ );
+ }
}
diff --git a/src/data/schematic.rs b/src/data/schematic.rs
index 20b7f60..6331606 100644
--- a/src/data/schematic.rs
+++ b/src/data/schematic.rs
@@ -1,1414 +1,1406 @@
use std::any::Any;
-use std::collections::HashMap;
use std::collections::hash_map::Entry;
+use std::collections::HashMap;
use std::error::Error;
use std::fmt::{self, Write};
use std::iter::FusedIterator;
use std::slice::Iter;
-use flate2::{Compress, CompressError, Compression, Decompress, DecompressError, FlushCompress, FlushDecompress, Status};
+use flate2::{
+ Compress, CompressError, Compression, Decompress, DecompressError, FlushCompress,
+ FlushDecompress, Status,
+};
use crate::block::{self, Block, BlockRegistry, Rotation};
-use crate::data::{self, DataRead, DataWrite, GridPos, Serializer};
use crate::data::base64;
use crate::data::dynamic::{self, DynData, DynSerializer};
+use crate::data::{self, DataRead, DataWrite, GridPos, Serializer};
use crate::item::storage::Storage as ItemStorage;
use crate::registry::RegistryEntry;
pub const MAX_DIMENSION: u16 = 128;
pub const MAX_BLOCKS: u32 = 128 * 128;
-pub struct Placement<'l>
-{
- pos: GridPos,
- block: &'l Block,
- state: Option<Box<dyn Any>>,
- rot: Rotation,
+pub struct Placement<'l> {
+ pos: GridPos,
+ block: &'l Block,
+ state: Option<Box<dyn Any>>,
+ rot: Rotation,
}
-impl<'l> Placement<'l>
-{
- pub fn get_pos(&self) -> GridPos
- {
- self.pos
- }
-
- pub fn get_block(&self) -> &'l Block
- {
- self.block
- }
-
- pub fn get_state(&self) -> Option<&dyn Any>
- {
- match self.state
- {
- None => None,
- Some(ref b) => Some(b.as_ref()),
- }
- }
-
- pub fn get_state_mut(&mut self) -> Option<&mut dyn Any>
- {
- match self.state
- {
- None => None,
- Some(ref mut b) => Some(b.as_mut()),
- }
- }
-
- pub fn set_state(&mut self, data: DynData) -> Result<Option<Box<dyn Any>>, block::DeserializeError>
- {
- let state = self.block.deserialize_state(data)?;
- Ok(std::mem::replace(&mut self.state, state))
- }
-
- pub fn get_rotation(&self) -> Rotation
- {
- self.rot
- }
-
- pub fn set_rotation(&mut self, rot: Rotation) -> Rotation
- {
- std::mem::replace(&mut self.rot, rot)
- }
+impl<'l> Placement<'l> {
+ pub fn get_pos(&self) -> GridPos {
+ self.pos
+ }
+
+ pub fn get_block(&self) -> &'l Block {
+ self.block
+ }
+
+ pub fn get_state(&self) -> Option<&dyn Any> {
+ match self.state {
+ None => None,
+ Some(ref b) => Some(b.as_ref()),
+ }
+ }
+
+ pub fn get_state_mut(&mut self) -> Option<&mut dyn Any> {
+ match self.state {
+ None => None,
+ Some(ref mut b) => Some(b.as_mut()),
+ }
+ }
+
+ pub fn set_state(
+ &mut self,
+ data: DynData,
+ ) -> Result<Option<Box<dyn Any>>, block::DeserializeError> {
+ let state = self.block.deserialize_state(data)?;
+ Ok(std::mem::replace(&mut self.state, state))
+ }
+
+ pub fn get_rotation(&self) -> Rotation {
+ self.rot
+ }
+
+ pub fn set_rotation(&mut self, rot: Rotation) -> Rotation {
+ std::mem::replace(&mut self.rot, rot)
+ }
}
// manual impl because trait objects cannot be cloned
-impl<'l> Clone for Placement<'l>
-{
- fn clone(&self) -> Self
- {
- Self
- {
- pos: self.pos,
- block: self.block,
- state: match self.state
- {
- None => None,
- Some(ref s) => Some(self.block.clone_state(s)),
- },
- rot: self.rot,
- }
- }
+impl<'l> Clone for Placement<'l> {
+ fn clone(&self) -> Self {
+ Self {
+ pos: self.pos,
+ block: self.block,
+ state: match self.state {
+ None => None,
+ Some(ref s) => Some(self.block.clone_state(s)),
+ },
+ rot: self.rot,
+ }
+ }
}
#[derive(Clone)]
-pub struct Schematic<'l>
-{
- width: u16,
- height: u16,
- tags: HashMap<String, String>,
- blocks: Vec<Placement<'l>>,
- lookup: Vec<Option<usize>>,
+pub struct Schematic<'l> {
+ width: u16,
+ height: u16,
+ tags: HashMap<String, String>,
+ blocks: Vec<Placement<'l>>,
+ lookup: Vec<Option<usize>>,
}
-impl<'l> Schematic<'l>
-{
- pub fn new(width: u16, height: u16) -> Self
- {
- match Self::try_new(width, height)
- {
- Ok(s) => s,
- Err(NewError::Width(w)) => panic!("invalid schematic width ({w})"),
- Err(NewError::Height(h)) => panic!("invalid schematic height ({h})"),
- }
- }
-
- pub fn try_new(width: u16, height: u16) -> Result<Self, NewError>
- {
- if width > MAX_DIMENSION
- {
- return Err(NewError::Width(width));
- }
- if height > MAX_DIMENSION
- {
- return Err(NewError::Height(height));
- }
- let mut tags = HashMap::<String, String>::new();
- tags.insert("name".to_string(), String::new());
- tags.insert("description".to_string(), String::new());
- tags.insert("labels".to_string(), "[]".to_string());
- Ok(Self{width, height, tags, blocks: Vec::new(), lookup: Vec::new()})
- }
-
- pub fn get_width(&self) -> u16
- {
- self.width
- }
-
- pub fn get_height(&self) -> u16
- {
- self.height
- }
-
- pub fn get_tags(&self) -> &HashMap<String, String>
- {
- &self.tags
- }
-
- pub fn get_tags_mut(&mut self) -> &mut HashMap<String, String>
- {
- &mut self.tags
- }
-
- pub fn is_empty(&self) -> bool
- {
- self.blocks.is_empty()
- }
-
- pub fn get_block_count(&self) -> usize
- {
- self.blocks.len()
- }
-
- pub fn is_region_empty(&self, x: u16, y: u16, w: u16, h: u16) -> bool
- {
- if self.blocks.len() == 0 {return true;}
- if x >= self.width || y >= self.height || w == 0 || h == 0 {return true;}
- if w > 1 || h > 1
- {
- let stride = self.width as usize;
- let x_end = if self.width - x > w {x + w} else {self.width} as usize;
- let y_end = if self.height - y > h {y + h} else {self.height} as usize;
- let x = x as usize;
- let y = y as usize;
- for cy in y..y_end
- {
- for cx in x..x_end
- {
- if self.lookup[cx + cy * stride].is_some() {return false;}
- }
- }
- true
- }
- else {self.lookup[(x as usize) + (y as usize) * (self.width as usize)].is_none()}
- }
-
- pub fn get(&self, x: u16, y: u16) -> Result<Option<&Placement<'l>>, PosError>
- {
- if x >= self.width || y >= self.height
- {
- return Err(PosError{x, y, w: self.width, h: self.height});
- }
- if self.blocks.len() == 0 {return Ok(None);}
- let pos = (x as usize) + (y as usize) * (self.width as usize);
- match self.lookup[pos]
- {
- None => Ok(None),
- Some(idx) => Ok(Some(&self.blocks[idx])),
- }
- }
-
- pub fn get_mut(&mut self, x: u16, y: u16) -> Result<Option<&mut Placement<'l>>, PosError>
- {
- if x >= self.width || y >= self.height
- {
- return Err(PosError{x, y, w: self.width, h: self.height});
- }
- if self.blocks.len() == 0 {return Ok(None);}
- let pos = (x as usize) + (y as usize) * (self.width as usize);
- match self.lookup[pos]
- {
- None => Ok(None),
- Some(idx) => Ok(Some(&mut self.blocks[idx])),
- }
- }
-
- fn swap_remove(&mut self, idx: usize) -> Placement<'l>
- {
- // swap_remove not only avoids moves in self.blocks but also reduces the lookup changes we have to do
- let prev = self.blocks.swap_remove(idx);
- self.fill_lookup(prev.pos.0 as usize, prev.pos.1 as usize, prev.block.get_size() as usize, None);
- if idx < self.blocks.len()
- {
- // fix the swapped block's lookup entries
- let swapped = &self.blocks[idx];
- self.fill_lookup(swapped.pos.0 as usize, swapped.pos.1 as usize, swapped.block.get_size() as usize, Some(idx));
- }
- prev
- }
-
- fn fill_lookup(&mut self, x: usize, y: usize, sz: usize, val: Option<usize>)
- {
- if self.lookup.len() == 0
- {
- self.lookup.resize((self.width as usize) * (self.height as usize), None);
- }
- if sz > 1
- {
- let off = ((sz - 1) / 2) as usize;
- let (x0, y0) = (x - off, y - off);
- for dy in 0..sz
- {
- for dx in 0..sz
- {
- self.lookup[(x0 + dx) + (y0 + dy) * (self.width as usize)] = val;
- }
- }
- }
- else {self.lookup[x + y * (self.width as usize)] = val;}
- }
-
- pub fn set(&mut self, x: u16, y: u16, block: &'l Block, data: DynData, rot: Rotation) -> Result<&Placement<'l>, PlaceError>
- {
- let sz = block.get_size() as u16;
- let off = (sz - 1) / 2;
- if x < off || y < off
- {
- return Err(PlaceError::Bounds{x, y, sz: block.get_size(), w: self.width, h: self.height});
- }
- if self.width - x < sz - off || self.height - y < sz - off
- {
- return Err(PlaceError::Bounds{x, y, sz: block.get_size(), w: self.width, h: self.height});
- }
- if self.is_region_empty(x - off, y - off, sz, sz)
- {
- let idx = self.blocks.len();
- let state = block.deserialize_state(data)?;
- self.blocks.push(Placement{pos: GridPos(x, y), block, state, rot});
- self.fill_lookup(x as usize, y as usize, block.get_size() as usize, Some(idx));
- Ok(&self.blocks[idx])
- }
- else {Err(PlaceError::Overlap{x, y})}
- }
-
- pub fn replace(&mut self, x: u16, y: u16, block: &'l Block, data: DynData, rot: Rotation, collect: bool)
- -> Result<Option<Vec<Placement<'l>>>, PlaceError>
- {
- let sz = block.get_size() as u16;
- let off = (sz - 1) / 2;
- if x < off || y < off
- {
- return Err(PlaceError::Bounds{x, y, sz: block.get_size(), w: self.width, h: self.height});
- }
- if self.width - x < sz - off || self.height - y < sz - off
- {
- return Err(PlaceError::Bounds{x, y, sz: block.get_size(), w: self.width, h: self.height});
- }
- if sz > 1
- {
- let mut result = if collect {Some(Vec::new())} else {None};
- // remove all blocks in the region
- for dy in 0..(sz as usize)
- {
- for dx in 0..(sz as usize)
- {
- if let Some(idx) = self.lookup[(x as usize + dx) + (y as usize + dy) * (self.width as usize)]
- {
- let prev = self.swap_remove(idx);
- if let Some(ref mut v) = result {v.push(prev);}
- }
- }
- }
- let idx = self.blocks.len();
- let state = block.deserialize_state(data)?;
- self.blocks.push(Placement{pos: GridPos(x, y), block, state, rot});
- self.fill_lookup(x as usize, y as usize, sz as usize, Some(idx));
- Ok(result)
- }
- else
- {
- let pos = (x as usize) + (y as usize) * (self.width as usize);
- match self.lookup[pos]
- {
- None =>
- {
- let idx = self.blocks.len();
- let state = block.deserialize_state(data)?;
- self.blocks.push(Placement{pos: GridPos(x, y), block, state, rot});
- self.lookup[pos] = Some(idx);
- Ok(if collect {Some(Vec::new())} else {None})
- },
- Some(idx) =>
- {
- let state = block.deserialize_state(data)?;
- let prev = std::mem::replace(&mut self.blocks[idx], Placement{pos: GridPos(x, y), block, state, rot});
- self.fill_lookup(prev.pos.0 as usize, prev.pos.1 as usize, prev.block.get_size() as usize, None);
- self.fill_lookup(x as usize, y as usize, sz as usize, Some(idx));
- Ok(if collect {Some(vec![prev])} else {None})
- }
- }
- }
- }
-
- pub fn take(&mut self, x: u16, y: u16) -> Result<Option<Placement<'l>>, PosError>
- {
- if x >= self.width || y >= self.height
- {
- return Err(PosError{x, y, w: self.width, h: self.height});
- }
- if self.blocks.len() > 0
- {
- let pos = (x as usize) + (y as usize) * (self.width as usize);
- match self.lookup[pos]
- {
- None => Ok(None),
- Some(idx) => Ok(Some(self.swap_remove(idx))),
- }
- }
- else {Ok(None)}
- }
-
- fn rebuild_lookup(&mut self)
- {
- self.lookup.clear();
- if self.blocks.len() > 0
- {
- self.lookup.resize((self.width as usize) * (self.height as usize), None);
- for (i, curr) in self.blocks.iter().enumerate()
- {
- let sz = curr.block.get_size() as usize;
- let x = curr.pos.0 as usize - (sz - 1) / 2;
- let y = curr.pos.1 as usize - (sz - 1) / 2;
- if sz > 1
- {
- for dy in 0..sz
- {
- for dx in 0..sz
- {
- self.lookup[(x + dx) + (y + dy) * (self.width as usize)] = Some(i);
- }
- }
- }
- else {self.lookup[x + y * (self.width as usize)] = Some(i);}
- }
- }
- }
-
- pub fn mirror(&mut self, horizontally: bool, vertically: bool)
- {
- if !self.blocks.is_empty() && (horizontally || vertically)
- {
- for curr in self.blocks.iter_mut()
- {
- // because position is the bottom left of the center (which changes during mirroring)
- let shift = (curr.block.get_size() as u16 - 1) % 2;
- if horizontally {curr.pos.0 = self.width - 1 - curr.pos.0 - shift;}
- if vertically {curr.pos.1 = self.height - 1 - curr.pos.1 - shift;}
- if !curr.block.is_symmetric() {curr.rot.mirror(horizontally, vertically);}
- if let Some(ref mut state) = curr.state
- {
- curr.block.mirror_state(state.as_mut(), horizontally, vertically);
- }
- }
- self.rebuild_lookup();
- }
- }
-
- pub fn rotate(&mut self, clockwise: bool)
- {
- let w = self.width;
- let h = self.height;
- self.width = h;
- self.height = w;
- if !self.blocks.is_empty()
- {
- for curr in self.blocks.iter_mut()
- {
- let x = curr.pos.0;
- let y = curr.pos.1;
- // because position is the bottom left of the center (which changes during rotation)
- let shift = (curr.block.get_size() as u16 - 1) % 2;
- if clockwise
- {
- curr.pos.0 = y;
- curr.pos.1 = w - 1 - x - shift;
- }
- else
- {
- curr.pos.0 = h - 1 - y - shift;
- curr.pos.1 = x;
- }
- if !curr.block.is_symmetric() {curr.rot.rotate(clockwise);}
- if let Some(ref mut state) = curr.state
- {
- curr.block.rotate_state(state.as_mut(), clockwise);
- }
- }
- self.rebuild_lookup();
- }
- }
-
- pub fn resize(&mut self, dx: i16, dy: i16, w: u16, h: u16) -> Result<(), ResizeError>
- {
- if w > MAX_DIMENSION
- {
- return Err(ResizeError::TargetWidth(w));
- }
- if h > MAX_DIMENSION
- {
- return Err(ResizeError::TargetHeight(h));
- }
- if dx <= -(w as i16) || dx >= self.width as i16
- {
- return Err(ResizeError::XOffset{dx, old_w: self.width, new_w: w});
- }
- if dy <= -(h as i16) || dy >= self.height as i16
- {
- return Err(ResizeError::YOffset{dy, old_h: self.height, new_h: h});
- }
- // check that all blocks fit into the new bounds
- let mut right = 0u16;
- let mut top = 0u16;
- let mut left = 0u16;
- let mut bottom = 0u16;
- let right_bound = dx + w as i16 - 1;
- let top_bound = dy + h as i16 - 1;
- let left_bound = dx;
- let bottom_bound = dy;
- for Placement{pos, block, ..} in self.blocks.iter()
- {
- let sz = block.get_size() as u16;
- let (x0, y0, x1, y1) = (pos.0 - (sz - 1) / 2, pos.1 - (sz - 1) / 2, pos.0 + sz / 2, pos.1 + sz / 2);
- if (x1 as i16) > right_bound && x1 - right_bound as u16 > right {right = x1 - right_bound as u16;}
- if (y1 as i16) > top_bound && y1 - top_bound as u16 > top {top = y1 - top_bound as u16;}
- if (x0 as i16) < left_bound && left_bound as u16 - x0 > left {left = left_bound as u16 - x0;}
- if (y0 as i16) < bottom_bound && bottom_bound as u16 - y0 > bottom {bottom = bottom_bound as u16 - y0;}
- }
- if left > 0 || top > 0 || left > 0 || bottom > 0
- {
- return Err(ResizeError::Truncated{right, top, left, bottom});
- }
- self.width = w;
- self.height = h;
- for Placement{pos, ..} in self.blocks.iter_mut()
- {
- pos.0 = (pos.0 as i16 + dx) as u16;
- pos.1 = (pos.1 as i16 + dy) as u16;
- }
- Ok(())
- }
-
- pub fn rotate_180(&mut self)
- {
- self.mirror(true, true);
- }
-
- pub fn pos_iter(&self) -> PosIter
- {
- PosIter{x: 0, y: 0, w: self.width, h: self.height}
- }
-
- pub fn block_iter<'s>(&'s self) -> Iter<'s, Placement<'l>>
- {
- self.blocks.iter()
- }
-
- pub fn compute_total_cost(&self) -> (ItemStorage, bool)
- {
- let mut cost = ItemStorage::new();
- let mut sandbox = false;
- for &Placement{block, ..} in self.blocks.iter()
- {
- if let Some(curr) = block.get_build_cost()
- {
- cost.add_all(curr, u32::MAX);
- }
- else {sandbox = true;}
- }
- (cost, sandbox)
- }
+impl<'l> Schematic<'l> {
+ pub fn new(width: u16, height: u16) -> Self {
+ match Self::try_new(width, height) {
+ Ok(s) => s,
+ Err(NewError::Width(w)) => panic!("invalid schematic width ({w})"),
+ Err(NewError::Height(h)) => panic!("invalid schematic height ({h})"),
+ }
+ }
+
+ pub fn try_new(width: u16, height: u16) -> Result<Self, NewError> {
+ if width > MAX_DIMENSION {
+ return Err(NewError::Width(width));
+ }
+ if height > MAX_DIMENSION {
+ return Err(NewError::Height(height));
+ }
+ let mut tags = HashMap::<String, String>::new();
+ tags.insert("name".to_string(), String::new());
+ tags.insert("description".to_string(), String::new());
+ tags.insert("labels".to_string(), "[]".to_string());
+ Ok(Self {
+ width,
+ height,
+ tags,
+ blocks: Vec::new(),
+ lookup: Vec::new(),
+ })
+ }
+
+ pub fn get_width(&self) -> u16 {
+ self.width
+ }
+
+ pub fn get_height(&self) -> u16 {
+ self.height
+ }
+
+ pub fn get_tags(&self) -> &HashMap<String, String> {
+ &self.tags
+ }
+
+ pub fn get_tags_mut(&mut self) -> &mut HashMap<String, String> {
+ &mut self.tags
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.blocks.is_empty()
+ }
+
+ pub fn get_block_count(&self) -> usize {
+ self.blocks.len()
+ }
+
+ pub fn is_region_empty(&self, x: u16, y: u16, w: u16, h: u16) -> bool {
+ if self.blocks.len() == 0 {
+ return true;
+ }
+ if x >= self.width || y >= self.height || w == 0 || h == 0 {
+ return true;
+ }
+ if w > 1 || h > 1 {
+ let stride = self.width as usize;
+ let x_end = if self.width - x > w {
+ x + w
+ } else {
+ self.width
+ } as usize;
+ let y_end = if self.height - y > h {
+ y + h
+ } else {
+ self.height
+ } as usize;
+ let x = x as usize;
+ let y = y as usize;
+ for cy in y..y_end {
+ for cx in x..x_end {
+ if self.lookup[cx + cy * stride].is_some() {
+ return false;
+ }
+ }
+ }
+ true
+ } else {
+ self.lookup[(x as usize) + (y as usize) * (self.width as usize)].is_none()
+ }
+ }
+
+ pub fn get(&self, x: u16, y: u16) -> Result<Option<&Placement<'l>>, PosError> {
+ if x >= self.width || y >= self.height {
+ return Err(PosError {
+ x,
+ y,
+ w: self.width,
+ h: self.height,
+ });
+ }
+ if self.blocks.len() == 0 {
+ return Ok(None);
+ }
+ let pos = (x as usize) + (y as usize) * (self.width as usize);
+ match self.lookup[pos] {
+ None => Ok(None),
+ Some(idx) => Ok(Some(&self.blocks[idx])),
+ }
+ }
+
+ pub fn get_mut(&mut self, x: u16, y: u16) -> Result<Option<&mut Placement<'l>>, PosError> {
+ if x >= self.width || y >= self.height {
+ return Err(PosError {
+ x,
+ y,
+ w: self.width,
+ h: self.height,
+ });
+ }
+ if self.blocks.len() == 0 {
+ return Ok(None);
+ }
+ let pos = (x as usize) + (y as usize) * (self.width as usize);
+ match self.lookup[pos] {
+ None => Ok(None),
+ Some(idx) => Ok(Some(&mut self.blocks[idx])),
+ }
+ }
+
+ fn swap_remove(&mut self, idx: usize) -> Placement<'l> {
+ // swap_remove not only avoids moves in self.blocks but also reduces the lookup changes we have to do
+ let prev = self.blocks.swap_remove(idx);
+ self.fill_lookup(
+ prev.pos.0 as usize,
+ prev.pos.1 as usize,
+ prev.block.get_size() as usize,
+ None,
+ );
+ if idx < self.blocks.len() {
+ // fix the swapped block's lookup entries
+ let swapped = &self.blocks[idx];
+ self.fill_lookup(
+ swapped.pos.0 as usize,
+ swapped.pos.1 as usize,
+ swapped.block.get_size() as usize,
+ Some(idx),
+ );
+ }
+ prev
+ }
+
+ fn fill_lookup(&mut self, x: usize, y: usize, sz: usize, val: Option<usize>) {
+ if self.lookup.len() == 0 {
+ self.lookup
+ .resize((self.width as usize) * (self.height as usize), None);
+ }
+ if sz > 1 {
+ let off = ((sz - 1) / 2) as usize;
+ let (x0, y0) = (x - off, y - off);
+ for dy in 0..sz {
+ for dx in 0..sz {
+ self.lookup[(x0 + dx) + (y0 + dy) * (self.width as usize)] = val;
+ }
+ }
+ } else {
+ self.lookup[x + y * (self.width as usize)] = val;
+ }
+ }
+
+ pub fn set(
+ &mut self,
+ x: u16,
+ y: u16,
+ block: &'l Block,
+ data: DynData,
+ rot: Rotation,
+ ) -> Result<&Placement<'l>, PlaceError> {
+ let sz = block.get_size() as u16;
+ let off = (sz - 1) / 2;
+ if x < off || y < off {
+ return Err(PlaceError::Bounds {
+ x,
+ y,
+ sz: block.get_size(),
+ w: self.width,
+ h: self.height,
+ });
+ }
+ if self.width - x < sz - off || self.height - y < sz - off {
+ return Err(PlaceError::Bounds {
+ x,
+ y,
+ sz: block.get_size(),
+ w: self.width,
+ h: self.height,
+ });
+ }
+ if self.is_region_empty(x - off, y - off, sz, sz) {
+ let idx = self.blocks.len();
+ let state = block.deserialize_state(data)?;
+ self.blocks.push(Placement {
+ pos: GridPos(x, y),
+ block,
+ state,
+ rot,
+ });
+ self.fill_lookup(x as usize, y as usize, block.get_size() as usize, Some(idx));
+ Ok(&self.blocks[idx])
+ } else {
+ Err(PlaceError::Overlap { x, y })
+ }
+ }
+
+ pub fn replace(
+ &mut self,
+ x: u16,
+ y: u16,
+ block: &'l Block,
+ data: DynData,
+ rot: Rotation,
+ collect: bool,
+ ) -> Result<Option<Vec<Placement<'l>>>, PlaceError> {
+ let sz = block.get_size() as u16;
+ let off = (sz - 1) / 2;
+ if x < off || y < off {
+ return Err(PlaceError::Bounds {
+ x,
+ y,
+ sz: block.get_size(),
+ w: self.width,
+ h: self.height,
+ });
+ }
+ if self.width - x < sz - off || self.height - y < sz - off {
+ return Err(PlaceError::Bounds {
+ x,
+ y,
+ sz: block.get_size(),
+ w: self.width,
+ h: self.height,
+ });
+ }
+ if sz > 1 {
+ let mut result = if collect { Some(Vec::new()) } else { None };
+ // remove all blocks in the region
+ for dy in 0..(sz as usize) {
+ for dx in 0..(sz as usize) {
+ if let Some(idx) =
+ self.lookup[(x as usize + dx) + (y as usize + dy) * (self.width as usize)]
+ {
+ let prev = self.swap_remove(idx);
+ if let Some(ref mut v) = result {
+ v.push(prev);
+ }
+ }
+ }
+ }
+ let idx = self.blocks.len();
+ let state = block.deserialize_state(data)?;
+ self.blocks.push(Placement {
+ pos: GridPos(x, y),
+ block,
+ state,
+ rot,
+ });
+ self.fill_lookup(x as usize, y as usize, sz as usize, Some(idx));
+ Ok(result)
+ } else {
+ let pos = (x as usize) + (y as usize) * (self.width as usize);
+ match self.lookup[pos] {
+ None => {
+ let idx = self.blocks.len();
+ let state = block.deserialize_state(data)?;
+ self.blocks.push(Placement {
+ pos: GridPos(x, y),
+ block,
+ state,
+ rot,
+ });
+ self.lookup[pos] = Some(idx);
+ Ok(if collect { Some(Vec::new()) } else { None })
+ }
+ Some(idx) => {
+ let state = block.deserialize_state(data)?;
+ let prev = std::mem::replace(
+ &mut self.blocks[idx],
+ Placement {
+ pos: GridPos(x, y),
+ block,
+ state,
+ rot,
+ },
+ );
+ self.fill_lookup(
+ prev.pos.0 as usize,
+ prev.pos.1 as usize,
+ prev.block.get_size() as usize,
+ None,
+ );
+ self.fill_lookup(x as usize, y as usize, sz as usize, Some(idx));
+ Ok(if collect { Some(vec![prev]) } else { None })
+ }
+ }
+ }
+ }
+
+ pub fn take(&mut self, x: u16, y: u16) -> Result<Option<Placement<'l>>, PosError> {
+ if x >= self.width || y >= self.height {
+ return Err(PosError {
+ x,
+ y,
+ w: self.width,
+ h: self.height,
+ });
+ }
+ if self.blocks.len() > 0 {
+ let pos = (x as usize) + (y as usize) * (self.width as usize);
+ match self.lookup[pos] {
+ None => Ok(None),
+ Some(idx) => Ok(Some(self.swap_remove(idx))),
+ }
+ } else {
+ Ok(None)
+ }
+ }
+
+ fn rebuild_lookup(&mut self) {
+ self.lookup.clear();
+ if self.blocks.len() > 0 {
+ self.lookup
+ .resize((self.width as usize) * (self.height as usize), None);
+ for (i, curr) in self.blocks.iter().enumerate() {
+ let sz = curr.block.get_size() as usize;
+ let x = curr.pos.0 as usize - (sz - 1) / 2;
+ let y = curr.pos.1 as usize - (sz - 1) / 2;
+ if sz > 1 {
+ for dy in 0..sz {
+ for dx in 0..sz {
+ self.lookup[(x + dx) + (y + dy) * (self.width as usize)] = Some(i);
+ }
+ }
+ } else {
+ self.lookup[x + y * (self.width as usize)] = Some(i);
+ }
+ }
+ }
+ }
+
+ pub fn mirror(&mut self, horizontally: bool, vertically: bool) {
+ if !self.blocks.is_empty() && (horizontally || vertically) {
+ for curr in self.blocks.iter_mut() {
+ // because position is the bottom left of the center (which changes during mirroring)
+ let shift = (curr.block.get_size() as u16 - 1) % 2;
+ if horizontally {
+ curr.pos.0 = self.width - 1 - curr.pos.0 - shift;
+ }
+ if vertically {
+ curr.pos.1 = self.height - 1 - curr.pos.1 - shift;
+ }
+ if !curr.block.is_symmetric() {
+ curr.rot.mirror(horizontally, vertically);
+ }
+ if let Some(ref mut state) = curr.state {
+ curr.block
+ .mirror_state(state.as_mut(), horizontally, vertically);
+ }
+ }
+ self.rebuild_lookup();
+ }
+ }
+
+ pub fn rotate(&mut self, clockwise: bool) {
+ let w = self.width;
+ let h = self.height;
+ self.width = h;
+ self.height = w;
+ if !self.blocks.is_empty() {
+ for curr in self.blocks.iter_mut() {
+ let x = curr.pos.0;
+ let y = curr.pos.1;
+ // because position is the bottom left of the center (which changes during rotation)
+ let shift = (curr.block.get_size() as u16 - 1) % 2;
+ if clockwise {
+ curr.pos.0 = y;
+ curr.pos.1 = w - 1 - x - shift;
+ } else {
+ curr.pos.0 = h - 1 - y - shift;
+ curr.pos.1 = x;
+ }
+ if !curr.block.is_symmetric() {
+ curr.rot.rotate(clockwise);
+ }
+ if let Some(ref mut state) = curr.state {
+ curr.block.rotate_state(state.as_mut(), clockwise);
+ }
+ }
+ self.rebuild_lookup();
+ }
+ }
+
+ pub fn resize(&mut self, dx: i16, dy: i16, w: u16, h: u16) -> Result<(), ResizeError> {
+ if w > MAX_DIMENSION {
+ return Err(ResizeError::TargetWidth(w));
+ }
+ if h > MAX_DIMENSION {
+ return Err(ResizeError::TargetHeight(h));
+ }
+ if dx <= -(w as i16) || dx >= self.width as i16 {
+ return Err(ResizeError::XOffset {
+ dx,
+ old_w: self.width,
+ new_w: w,
+ });
+ }
+ if dy <= -(h as i16) || dy >= self.height as i16 {
+ return Err(ResizeError::YOffset {
+ dy,
+ old_h: self.height,
+ new_h: h,
+ });
+ }
+ // check that all blocks fit into the new bounds
+ let mut right = 0u16;
+ let mut top = 0u16;
+ let mut left = 0u16;
+ let mut bottom = 0u16;
+ let right_bound = dx + w as i16 - 1;
+ let top_bound = dy + h as i16 - 1;
+ let left_bound = dx;
+ let bottom_bound = dy;
+ for Placement { pos, block, .. } in self.blocks.iter() {
+ let sz = block.get_size() as u16;
+ let (x0, y0, x1, y1) = (
+ pos.0 - (sz - 1) / 2,
+ pos.1 - (sz - 1) / 2,
+ pos.0 + sz / 2,
+ pos.1 + sz / 2,
+ );
+ if (x1 as i16) > right_bound && x1 - right_bound as u16 > right {
+ right = x1 - right_bound as u16;
+ }
+ if (y1 as i16) > top_bound && y1 - top_bound as u16 > top {
+ top = y1 - top_bound as u16;
+ }
+ if (x0 as i16) < left_bound && left_bound as u16 - x0 > left {
+ left = left_bound as u16 - x0;
+ }
+ if (y0 as i16) < bottom_bound && bottom_bound as u16 - y0 > bottom {
+ bottom = bottom_bound as u16 - y0;
+ }
+ }
+ if left > 0 || top > 0 || left > 0 || bottom > 0 {
+ return Err(ResizeError::Truncated {
+ right,
+ top,
+ left,
+ bottom,
+ });
+ }
+ self.width = w;
+ self.height = h;
+ for Placement { pos, .. } in self.blocks.iter_mut() {
+ pos.0 = (pos.0 as i16 + dx) as u16;
+ pos.1 = (pos.1 as i16 + dy) as u16;
+ }
+ Ok(())
+ }
+
+ pub fn rotate_180(&mut self) {
+ self.mirror(true, true);
+ }
+
+ pub fn pos_iter(&self) -> PosIter {
+ PosIter {
+ x: 0,
+ y: 0,
+ w: self.width,
+ h: self.height,
+ }
+ }
+
+ pub fn block_iter<'s>(&'s self) -> Iter<'s, Placement<'l>> {
+ self.blocks.iter()
+ }
+
+ pub fn compute_total_cost(&self) -> (ItemStorage, bool) {
+ let mut cost = ItemStorage::new();
+ let mut sandbox = false;
+ for &Placement { block, .. } in self.blocks.iter() {
+ if let Some(curr) = block.get_build_cost() {
+ cost.add_all(curr, u32::MAX);
+ } else {
+ sandbox = true;
+ }
+ }
+ (cost, sandbox)
+ }
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-pub enum NewError
-{
- Width(u16),
- Height(u16),
+pub enum NewError {
+ Width(u16),
+ Height(u16),
}
-impl fmt::Display for NewError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Width(w) => write!(f, "invalid schematic width ({w})"),
- Self::Height(h) => write!(f, "invalid schematic height ({h})"),
- }
- }
+impl fmt::Display for NewError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Width(w) => write!(f, "invalid schematic width ({w})"),
+ Self::Height(h) => write!(f, "invalid schematic height ({h})"),
+ }
+ }
}
impl Error for NewError {}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-pub struct PosError
-{
- pub x: u16,
- pub y: u16,
- pub w: u16,
- pub h: u16,
+pub struct PosError {
+ pub x: u16,
+ pub y: u16,
+ pub w: u16,
+ pub h: u16,
}
-impl fmt::Display for PosError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- write!(f, "position {x} / {y} out of bounds {w} / {h}", x = self.x, y = self.y, w = self.w, h = self.h)
- }
+impl fmt::Display for PosError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "position {x} / {y} out of bounds {w} / {h}",
+ x = self.x,
+ y = self.y,
+ w = self.w,
+ h = self.h
+ )
+ }
}
impl Error for PosError {}
#[derive(Debug)]
-pub enum PlaceError
-{
- Bounds{x: u16, y: u16, sz: u8, w: u16, h: u16},
- Overlap{x: u16, y: u16},
- Deserialize(block::DeserializeError),
+pub enum PlaceError {
+ Bounds {
+ x: u16,
+ y: u16,
+ sz: u8,
+ w: u16,
+ h: u16,
+ },
+ Overlap {
+ x: u16,
+ y: u16,
+ },
+ Deserialize(block::DeserializeError),
}
-impl From<block::DeserializeError> for PlaceError
-{
- fn from(value: block::DeserializeError) -> Self
- {
- PlaceError::Deserialize(value)
- }
+impl From<block::DeserializeError> for PlaceError {
+ fn from(value: block::DeserializeError) -> Self {
+ PlaceError::Deserialize(value)
+ }
}
-impl fmt::Display for PlaceError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Bounds{x, y, sz, w, h} => write!(f, "invalid block placement {x} / {y} (size {sz}) within {w} / {h}"),
- Self::Overlap{x, y} => write!(f, "overlapping an existing block at {x} / {y}"),
- Self::Deserialize(..) => f.write_str("block state deserialization failed"),
- }
- }
+impl fmt::Display for PlaceError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Bounds { x, y, sz, w, h } => write!(
+ f,
+ "invalid block placement {x} / {y} (size {sz}) within {w} / {h}"
+ ),
+ Self::Overlap { x, y } => write!(f, "overlapping an existing block at {x} / {y}"),
+ Self::Deserialize(..) => f.write_str("block state deserialization failed"),
+ }
+ }
}
-impl Error for PlaceError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- PlaceError::Deserialize(e) => Some(e),
- _ => None,
- }
- }
+impl Error for PlaceError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ PlaceError::Deserialize(e) => Some(e),
+ _ => None,
+ }
+ }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum ResizeError
-{
- TargetWidth(u16),
- TargetHeight(u16),
- XOffset{dx: i16, old_w: u16, new_w: u16},
- YOffset{dy: i16, old_h: u16, new_h: u16},
- Truncated{right: u16, top: u16, left: u16, bottom: u16},
+pub enum ResizeError {
+ TargetWidth(u16),
+ TargetHeight(u16),
+ XOffset {
+ dx: i16,
+ old_w: u16,
+ new_w: u16,
+ },
+ YOffset {
+ dy: i16,
+ old_h: u16,
+ new_h: u16,
+ },
+ Truncated {
+ right: u16,
+ top: u16,
+ left: u16,
+ bottom: u16,
+ },
}
-impl fmt::Display for ResizeError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::TargetWidth(w) => write!(f, "invalid target width ({w})"),
- Self::TargetHeight(w) => write!(f, "invalid target height ({w})"),
- Self::XOffset{dx, old_w, new_w} => write!(f, "horizontal offset {dx} not in [-{new_w}, {old_w}]"),
- Self::YOffset{dy, old_h, new_h} => write!(f, "vertical offset {dy} not in [-{new_h}, {old_h}]"),
- Self::Truncated{right, top, left, bottom} =>
- {
- macro_rules!fmt_dir
- {
- ($f:ident, $first:ident, $name:expr, $value:expr) =>
- {
- if $value != 0
- {
- if $first
- {
- f.write_str(" (")?;
- $first = false;
- }
- else {f.write_str(", ")?;}
- write!(f, "{}: {}", $name, $value)?;
- }
- };
- }
-
- f.write_str("truncated blocks")?;
- let mut first = true;
- fmt_dir!(f, first, "right", *right);
- fmt_dir!(f, first, "top", *top);
- fmt_dir!(f, first, "left", *left);
- fmt_dir!(f, first, "bottom", *bottom);
- if !first {f.write_char(')')?;}
- Ok(())
- },
- }
- }
+impl fmt::Display for ResizeError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::TargetWidth(w) => write!(f, "invalid target width ({w})"),
+ Self::TargetHeight(w) => write!(f, "invalid target height ({w})"),
+ Self::XOffset { dx, old_w, new_w } => {
+ write!(f, "horizontal offset {dx} not in [-{new_w}, {old_w}]")
+ }
+ Self::YOffset { dy, old_h, new_h } => {
+ write!(f, "vertical offset {dy} not in [-{new_h}, {old_h}]")
+ }
+ Self::Truncated {
+ right,
+ top,
+ left,
+ bottom,
+ } => {
+ macro_rules! fmt_dir {
+ ($f:ident, $first:ident, $name:expr, $value:expr) => {
+ if $value != 0 {
+ if $first {
+ f.write_str(" (")?;
+ $first = false;
+ } else {
+ f.write_str(", ")?;
+ }
+ write!(f, "{}: {}", $name, $value)?;
+ }
+ };
+ }
+
+ f.write_str("truncated blocks")?;
+ let mut first = true;
+ fmt_dir!(f, first, "right", *right);
+ fmt_dir!(f, first, "top", *top);
+ fmt_dir!(f, first, "left", *left);
+ fmt_dir!(f, first, "bottom", *bottom);
+ if !first {
+ f.write_char(')')?;
+ }
+ Ok(())
+ }
+ }
+ }
}
impl Error for ResizeError {}
-impl<'l> fmt::Display for Schematic<'l>
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- /*
- Because characters are about twice as tall as they are wide, two are used to represent a single block.
- Each block has a single letter to describe what it is + an optional rotation.
- For size-1 blocks, that's "*]" for symmetric and "*>", "*^", "*<", "*v" for rotations.
- Larger blocks are formed using pipes, slashes and minuses to form a border, which is filled with spaces.
- Then, the letter is placed inside followed by the rotation (if any).
- */
-
- // find unique letters for each block, more common blocks pick first
- let mut name_cnt = HashMap::<&str, u16>::new();
- for p in self.blocks.iter()
- {
- match name_cnt.entry(p.block.get_name())
- {
- Entry::Occupied(mut e) => *e.get_mut() += 1,
- Entry::Vacant(e) => {e.insert(1);},
- }
- }
- // only needed the map for counting
- let mut name_cnt = Vec::from_iter(name_cnt);
- name_cnt.sort_by(|l, r| r.1.cmp(&l.1));
- // set for control characters, space, b'*', DEL and b">^<v]/|\\-"
- let mut used = [0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0x01u8, 0xA4u8, 0x00u8, 0x50u8, 0x00u8, 0x00u8, 0x00u8, 0x70u8, 0x00u8, 0x00u8, 0x40u8, 0x90u8];
- let mut types = HashMap::<&str, char>::new();
- for &(name, _) in name_cnt.iter()
- {
- let mut found = false;
- for c in name.chars()
- {
- if c > ' ' && c <= '~'
- {
- let upper = c.to_ascii_uppercase() as usize;
- let lower = c.to_ascii_lowercase() as usize;
- if used[upper >> 3] & (1 << (upper & 7)) == 0
- {
- found = true;
- used[upper >> 3] |= 1 << (upper & 7);
- types.insert(name, unsafe{char::from_u32_unchecked(upper as u32)});
- break;
- }
- if lower != upper && used[lower >> 3] & (1 << (lower & 7)) == 0
- {
- found = true;
- used[lower >> 3] |= 1 << (lower & 7);
- types.insert(name, unsafe{char::from_u32_unchecked(lower as u32)});
- break;
- }
- }
- }
- if !found
- {
- // just take whatever symbol's still free (avoids collisions with letters)
- match used.iter().enumerate().find(|(_, &v)| v != u8::MAX)
- {
- // there's no more free symbols... how? use b'*' instead for all of them (reserved)
- None => {types.insert(name, '*');},
- Some((i, v)) =>
- {
- let idx = i + v.trailing_ones() as usize;
- used[idx >> 3] |= 1 << (idx & 7);
- types.insert(name, unsafe{char::from_u32_unchecked(idx as u32)});
- },
- }
- }
- }
-
- // coordinates start in the bottom left, so y starts at self.height - 1
- if self.blocks.len() > 0
- {
- for y in (0..self.height as usize).rev()
- {
- let mut x = 0usize;
- while x < self.width as usize
- {
- if let Some(idx) = self.lookup[x + y * (self.width as usize)]
- {
- let Placement{pos, block, state: _, rot} = self.blocks[idx];
- let c = *types.get(block.get_name()).unwrap();
- match block.get_size() as usize
- {
- 0 => unreachable!(),
- 1 =>
- {
- f.write_char(c)?;
- match rot
- {
- _ if block.is_symmetric() => f.write_char(']')?,
- Rotation::Right => f.write_char('>')?,
- Rotation::Up => f.write_char('^')?,
- Rotation::Left => f.write_char('<')?,
- Rotation::Down => f.write_char('v')?,
- }
- },
- s =>
- {
- let y0 = pos.1 as usize - (s - 1) / 2;
- if y == y0 + (s - 1)
- {
- // top row, which looks like /---[...]---\
- f.write_char('/')?;
- if s == 2
- {
- // label & rotation are in this row
- f.write_char(c)?;
- match rot
- {
- _ if block.is_symmetric() => f.write_char('-')?,
- Rotation::Right => f.write_char('>')?,
- Rotation::Up => f.write_char('^')?,
- Rotation::Left => f.write_char('<')?,
- Rotation::Down => f.write_char('v')?,
- }
- }
- else
- {
- // label & rotation are not in this row
- for _ in 0..(2 * s - 2)
- {
- f.write_char('-')?;
- }
- }
- f.write_char('\\')?;
- }
- else if y == y0
- {
- // bottom row, which looks like \---[...]---/
- f.write_char('\\')?;
- for _ in 0..(2 * s - 2)
- {
- f.write_char('-')?;
- }
- f.write_char('/')?;
- }
- else if s > 2 && y == y0 + s / 2
- {
- // middle row with label
- f.write_char('|')?;
- for cx in 0..(2 * s - 2)
- {
- if cx == s - 2 {f.write_char(c)?;}
- else if cx == s - 1
- {
- match rot
- {
- _ if block.is_symmetric() => f.write_char(' ')?,
- Rotation::Right => f.write_char('>')?,
- Rotation::Up => f.write_char('^')?,
- Rotation::Left => f.write_char('<')?,
- Rotation::Down => f.write_char('v')?,
- }
- }
- else {f.write_char(' ')?;}
- }
- f.write_char('|')?;
- }
- else
- {
- // middle row, which looks like | [...] |
- f.write_char('|')?;
- for _ in 0..(2 * s - 2)
- {
- f.write_char(' ')?;
- }
- f.write_char('|')?;
- }
- },
- }
- x += block.get_size() as usize;
- }
- else
- {
- f.write_str(" ")?;
- x += 1;
- }
- }
- writeln!(f)?;
- }
- // print the letters assigned to blocks
- for (k, _) in name_cnt
- {
- let v = *types.get(k).unwrap();
- write!(f, "\n({v}) {k}")?;
- }
- }
- else {write!(f, "<empty {} * {}>", self.width, self.height)?;}
- Ok(())
- }
+impl<'l> fmt::Display for Schematic<'l> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ /*
+ Because characters are about twice as tall as they are wide, two are used to represent a single block.
+ Each block has a single letter to describe what it is + an optional rotation.
+ For size-1 blocks, that's "*]" for symmetric and "*>", "*^", "*<", "*v" for rotations.
+ Larger blocks are formed using pipes, slashes and minuses to form a border, which is filled with spaces.
+ Then, the letter is placed inside followed by the rotation (if any).
+ */
+
+ // find unique letters for each block, more common blocks pick first
+ let mut name_cnt = HashMap::<&str, u16>::new();
+ for p in self.blocks.iter() {
+ match name_cnt.entry(p.block.get_name()) {
+ Entry::Occupied(mut e) => *e.get_mut() += 1,
+ Entry::Vacant(e) => {
+ e.insert(1);
+ }
+ }
+ }
+ // only needed the map for counting
+ let mut name_cnt = Vec::from_iter(name_cnt);
+ name_cnt.sort_by(|l, r| r.1.cmp(&l.1));
+ // set for control characters, space, b'*', DEL and b">^<v]/|\\-"
+ let mut used = [
+ 0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0x01u8, 0xA4u8, 0x00u8, 0x50u8, 0x00u8, 0x00u8, 0x00u8,
+ 0x70u8, 0x00u8, 0x00u8, 0x40u8, 0x90u8,
+ ];
+ let mut types = HashMap::<&str, char>::new();
+ for &(name, _) in name_cnt.iter() {
+ let mut found = false;
+ for c in name.chars() {
+ if c > ' ' && c <= '~' {
+ let upper = c.to_ascii_uppercase() as usize;
+ let lower = c.to_ascii_lowercase() as usize;
+ if used[upper >> 3] & (1 << (upper & 7)) == 0 {
+ found = true;
+ used[upper >> 3] |= 1 << (upper & 7);
+ types.insert(name, unsafe { char::from_u32_unchecked(upper as u32) });
+ break;
+ }
+ if lower != upper && used[lower >> 3] & (1 << (lower & 7)) == 0 {
+ found = true;
+ used[lower >> 3] |= 1 << (lower & 7);
+ types.insert(name, unsafe { char::from_u32_unchecked(lower as u32) });
+ break;
+ }
+ }
+ }
+ if !found {
+ // just take whatever symbol's still free (avoids collisions with letters)
+ match used.iter().enumerate().find(|(_, &v)| v != u8::MAX) {
+ // there's no more free symbols... how? use b'*' instead for all of them (reserved)
+ None => {
+ types.insert(name, '*');
+ }
+ Some((i, v)) => {
+ let idx = i + v.trailing_ones() as usize;
+ used[idx >> 3] |= 1 << (idx & 7);
+ types.insert(name, unsafe { char::from_u32_unchecked(idx as u32) });
+ }
+ }
+ }
+ }
+
+ // coordinates start in the bottom left, so y starts at self.height - 1
+ if self.blocks.len() > 0 {
+ for y in (0..self.height as usize).rev() {
+ let mut x = 0usize;
+ while x < self.width as usize {
+ if let Some(idx) = self.lookup[x + y * (self.width as usize)] {
+ let Placement {
+ pos,
+ block,
+ state: _,
+ rot,
+ } = self.blocks[idx];
+ let c = *types.get(block.get_name()).unwrap();
+ match block.get_size() as usize {
+ 0 => unreachable!(),
+ 1 => {
+ f.write_char(c)?;
+ match rot {
+ _ if block.is_symmetric() => f.write_char(']')?,
+ Rotation::Right => f.write_char('>')?,
+ Rotation::Up => f.write_char('^')?,
+ Rotation::Left => f.write_char('<')?,
+ Rotation::Down => f.write_char('v')?,
+ }
+ }
+ s => {
+ let y0 = pos.1 as usize - (s - 1) / 2;
+ if y == y0 + (s - 1) {
+ // top row, which looks like /---[...]---\
+ f.write_char('/')?;
+ if s == 2 {
+ // label & rotation are in this row
+ f.write_char(c)?;
+ match rot {
+ _ if block.is_symmetric() => f.write_char('-')?,
+ Rotation::Right => f.write_char('>')?,
+ Rotation::Up => f.write_char('^')?,
+ Rotation::Left => f.write_char('<')?,
+ Rotation::Down => f.write_char('v')?,
+ }
+ } else {
+ // label & rotation are not in this row
+ for _ in 0..(2 * s - 2) {
+ f.write_char('-')?;
+ }
+ }
+ f.write_char('\\')?;
+ } else if y == y0 {
+ // bottom row, which looks like \---[...]---/
+ f.write_char('\\')?;
+ for _ in 0..(2 * s - 2) {
+ f.write_char('-')?;
+ }
+ f.write_char('/')?;
+ } else if s > 2 && y == y0 + s / 2 {
+ // middle row with label
+ f.write_char('|')?;
+ for cx in 0..(2 * s - 2) {
+ if cx == s - 2 {
+ f.write_char(c)?;
+ } else if cx == s - 1 {
+ match rot {
+ _ if block.is_symmetric() => f.write_char(' ')?,
+ Rotation::Right => f.write_char('>')?,
+ Rotation::Up => f.write_char('^')?,
+ Rotation::Left => f.write_char('<')?,
+ Rotation::Down => f.write_char('v')?,
+ }
+ } else {
+ f.write_char(' ')?;
+ }
+ }
+ f.write_char('|')?;
+ } else {
+ // middle row, which looks like | [...] |
+ f.write_char('|')?;
+ for _ in 0..(2 * s - 2) {
+ f.write_char(' ')?;
+ }
+ f.write_char('|')?;
+ }
+ }
+ }
+ x += block.get_size() as usize;
+ } else {
+ f.write_str(" ")?;
+ x += 1;
+ }
+ }
+ writeln!(f)?;
+ }
+ // print the letters assigned to blocks
+ for (k, _) in name_cnt {
+ let v = *types.get(k).unwrap();
+ write!(f, "\n({v}) {k}")?;
+ }
+ } else {
+ write!(f, "<empty {} * {}>", self.width, self.height)?;
+ }
+ Ok(())
+ }
}
-const SCHEMATIC_HEADER: u32 = ((b'm' as u32) << 24) | ((b's' as u32) << 16) | ((b'c' as u32) << 8) | (b'h' as u32);
+const SCHEMATIC_HEADER: u32 =
+ ((b'm' as u32) << 24) | ((b's' as u32) << 16) | ((b'c' as u32) << 8) | (b'h' as u32);
pub struct SchematicSerializer<'l>(pub &'l BlockRegistry<'l>);
-impl<'l> Serializer<Schematic<'l>> for SchematicSerializer<'l>
-{
- type ReadError = ReadError;
- type WriteError = WriteError;
-
- fn deserialize(&mut self, buff: &mut DataRead<'_>) -> Result<Schematic<'l>, Self::ReadError>
- {
- let hdr = buff.read_u32()?;
- if hdr != SCHEMATIC_HEADER {return Err(ReadError::Header(hdr));}
- let version = buff.read_u8()?;
- if version > 1 {return Err(ReadError::Version(version));}
- let mut dec = Decompress::new(true);
- let mut raw = Vec::<u8>::new();
- raw.reserve(1024);
- loop
- {
- let t_in = dec.total_in();
- let t_out = dec.total_out();
- let res = dec.decompress_vec(buff.data, &mut raw, FlushDecompress::Finish)?;
- if dec.total_in() > t_in
- {
- // we have to advance input every time, decompress_vec only knows the output position
- buff.data = &buff.data[(dec.total_in() - t_in) as usize..];
- }
- match res
- {
- // there's no more input (and the flush mode says so), we need to reserve additional space
- Status::Ok | Status::BufError => (),
- // input was already at the end, so this is referring to the output
- Status::StreamEnd => break,
- }
- if dec.total_in() == t_in && dec.total_out() == t_out
- {
- // protect against looping forever
- return Err(ReadError::DecompressStall);
- }
- raw.reserve(1024);
- }
- assert_eq!(dec.total_out() as usize, raw.len());
- let mut rbuff = DataRead::new(&raw);
- let w = rbuff.read_i16()?;
- let h = rbuff.read_i16()?;
- if w < 0 || h < 0 || w as u16 > MAX_DIMENSION || h as u16 > MAX_DIMENSION
- {
- return Err(ReadError::Dimensions(w, h));
- }
- let mut schematic = Schematic::new(w as u16, h as u16);
- for _ in 0..rbuff.read_u8()?
- {
- let key = rbuff.read_utf()?;
- let value = rbuff.read_utf()?;
- schematic.tags.insert(key.to_owned(), value.to_owned());
- }
- let num_table = rbuff.read_i8()?;
- if num_table < 0
- {
- return Err(ReadError::TableSize(num_table));
- }
- let mut block_table = Vec::<&'l Block>::new();
- block_table.reserve(num_table as usize);
- for _ in 0..num_table
- {
- let name = rbuff.read_utf()?;
- match self.0.get(name)
- {
- None => return Err(ReadError::NoSuchBlock(name.to_owned())),
- Some(b) => block_table.push(b),
- }
- }
- let num_blocks = rbuff.read_i32()?;
- if num_blocks < 0 || num_blocks as u32 > MAX_BLOCKS
- {
- return Err(ReadError::BlockCount(num_blocks));
- }
- for _ in 0..num_blocks
- {
- let idx = rbuff.read_i8()?;
- if idx < 0 || idx as usize >= block_table.len()
- {
- return Err(ReadError::BlockIndex(idx, block_table.len()));
- }
- let pos = GridPos::from(rbuff.read_u32()?);
- let block = block_table[idx as usize];
- let config = if version < 1
- {
- block.data_from_i32(rbuff.read_i32()?, pos)?
- }
- else {DynSerializer.deserialize(&mut rbuff)?};
- let rot = Rotation::from(rbuff.read_u8()?);
- schematic.set(pos.0, pos.1, block, config, rot)?;
- }
- Ok(schematic)
- }
-
- fn serialize(&mut self, buff: &mut DataWrite<'_>, data: &Schematic) -> Result<(), Self::WriteError>
- {
- // write the header first just in case
- buff.write_u32(SCHEMATIC_HEADER)?;
- buff.write_u8(1)?;
-
- let mut rbuff = DataWrite::new();
- // don't have to check dimensions because they're already limited to MAX_DIMENSION
- rbuff.write_i16(data.width as i16)?;
- rbuff.write_i16(data.height as i16)?;
- if data.tags.len() > u8::MAX as usize
- {
- return Err(WriteError::TagCount(data.tags.len()));
- }
- rbuff.write_u8(data.tags.len() as u8)?;
- for (k, v) in data.tags.iter()
- {
- rbuff.write_utf(k)?;
- rbuff.write_utf(v)?;
- }
- // use string keys here to avoid issues with different block refs with the same name
- let mut block_map = HashMap::<&str, u32>::new();
- let mut block_table = Vec::<&str>::new();
- for curr in data.blocks.iter()
- {
- match block_map.entry(curr.block.get_name())
- {
- Entry::Vacant(e) =>
- {
- e.insert(block_table.len() as u32);
- block_table.push(curr.block.get_name());
- },
- _ => (),
- }
- }
- if block_table.len() > i8::MAX as usize
- {
- return Err(WriteError::TableSize(block_table.len()));
- }
- // else: implies contents are also valid i8 (they're strictly less than the map length)
- rbuff.write_i8(block_table.len() as i8)?;
- for &name in block_table.iter()
- {
- rbuff.write_utf(name)?;
- }
- // don't have to check data.blocks.len() because dimensions don't allow exceeding MAX_BLOCKS
- rbuff.write_i32(data.blocks.len() as i32)?;
- let mut num = 0;
- for curr in data.blocks.iter()
- {
- rbuff.write_i8(block_map[curr.block.get_name()] as i8)?;
- rbuff.write_u32(u32::from(curr.pos))?;
- let data = match curr.state
- {
- None => DynData::Empty,
- Some(ref s) => curr.block.serialize_state(s.as_ref())?,
- };
- DynSerializer.serialize(&mut rbuff, &data)?;
- rbuff.write_u8(curr.rot.into())?;
- num += 1;
- }
- assert_eq!(num, data.blocks.len());
-
- // compress into the provided buffer
- let raw = match rbuff.data
- {
- data::WriteBuff::Vec(v) => v,
- _ => unreachable!("write buffer not owned"),
- };
- let mut comp = Compress::new(Compression::default(), true);
- // compress the immediate buffer into a temp buffer to copy it to buff? no thanks
- match buff.data
- {
- data::WriteBuff::Ref{raw: ref mut dst, ref mut pos} =>
- {
- match comp.compress(&raw, &mut dst[*pos..], FlushCompress::Finish)?
- {
- // there's no more input (and the flush mode says so), but we can't resize the output
- Status::Ok | Status::BufError => return Err(WriteError::CompressEof(raw.len() - comp.total_in() as usize)),
- Status::StreamEnd => (),
- }
- },
- data::WriteBuff::Vec(ref mut dst) =>
- {
- let mut input = raw.as_ref();
- dst.reserve(1024);
- loop
- {
- let t_in = comp.total_in();
- let t_out = comp.total_out();
- let res = comp.compress_vec(input, dst, FlushCompress::Finish)?;
- if comp.total_in() > t_in
- {
- // we have to advance input every time, compress_vec only knows the output position
- input = &input[(comp.total_in() - t_in) as usize..];
- }
- match res
- {
- // there's no more input (and the flush mode says so), we need to reserve additional space
- Status::Ok | Status::BufError => (),
- // input was already at the end, so this is referring to the output
- Status::StreamEnd => break,
- }
- if comp.total_in() == t_in && comp.total_out() == t_out
- {
- // protect against looping forever
- return Err(WriteError::CompressStall);
- }
- dst.reserve(1024);
- }
- },
- }
- assert_eq!(comp.total_in() as usize, raw.len());
- Ok(())
- }
+impl<'l> Serializer<Schematic<'l>> for SchematicSerializer<'l> {
+ type ReadError = ReadError;
+ type WriteError = WriteError;
+
+ fn deserialize(&mut self, buff: &mut DataRead<'_>) -> Result<Schematic<'l>, Self::ReadError> {
+ let hdr = buff.read_u32()?;
+ if hdr != SCHEMATIC_HEADER {
+ return Err(ReadError::Header(hdr));
+ }
+ let version = buff.read_u8()?;
+ if version > 1 {
+ return Err(ReadError::Version(version));
+ }
+ let mut dec = Decompress::new(true);
+ let mut raw = Vec::<u8>::new();
+ raw.reserve(1024);
+ loop {
+ let t_in = dec.total_in();
+ let t_out = dec.total_out();
+ let res = dec.decompress_vec(buff.data, &mut raw, FlushDecompress::Finish)?;
+ if dec.total_in() > t_in {
+ // we have to advance input every time, decompress_vec only knows the output position
+ buff.data = &buff.data[(dec.total_in() - t_in) as usize..];
+ }
+ match res {
+ // there's no more input (and the flush mode says so), we need to reserve additional space
+ Status::Ok | Status::BufError => (),
+ // input was already at the end, so this is referring to the output
+ Status::StreamEnd => break,
+ }
+ if dec.total_in() == t_in && dec.total_out() == t_out {
+ // protect against looping forever
+ return Err(ReadError::DecompressStall);
+ }
+ raw.reserve(1024);
+ }
+ assert_eq!(dec.total_out() as usize, raw.len());
+ let mut rbuff = DataRead::new(&raw);
+ let w = rbuff.read_i16()?;
+ let h = rbuff.read_i16()?;
+ if w < 0 || h < 0 || w as u16 > MAX_DIMENSION || h as u16 > MAX_DIMENSION {
+ return Err(ReadError::Dimensions(w, h));
+ }
+ let mut schematic = Schematic::new(w as u16, h as u16);
+ for _ in 0..rbuff.read_u8()? {
+ let key = rbuff.read_utf()?;
+ let value = rbuff.read_utf()?;
+ schematic.tags.insert(key.to_owned(), value.to_owned());
+ }
+ let num_table = rbuff.read_i8()?;
+ if num_table < 0 {
+ return Err(ReadError::TableSize(num_table));
+ }
+ let mut block_table = Vec::<&'l Block>::new();
+ block_table.reserve(num_table as usize);
+ for _ in 0..num_table {
+ let name = rbuff.read_utf()?;
+ match self.0.get(name) {
+ None => return Err(ReadError::NoSuchBlock(name.to_owned())),
+ Some(b) => block_table.push(b),
+ }
+ }
+ let num_blocks = rbuff.read_i32()?;
+ if num_blocks < 0 || num_blocks as u32 > MAX_BLOCKS {
+ return Err(ReadError::BlockCount(num_blocks));
+ }
+ for _ in 0..num_blocks {
+ let idx = rbuff.read_i8()?;
+ if idx < 0 || idx as usize >= block_table.len() {
+ return Err(ReadError::BlockIndex(idx, block_table.len()));
+ }
+ let pos = GridPos::from(rbuff.read_u32()?);
+ let block = block_table[idx as usize];
+ let config = if version < 1 {
+ block.data_from_i32(rbuff.read_i32()?, pos)?
+ } else {
+ DynSerializer.deserialize(&mut rbuff)?
+ };
+ let rot = Rotation::from(rbuff.read_u8()?);
+ schematic.set(pos.0, pos.1, block, config, rot)?;
+ }
+ Ok(schematic)
+ }
+
+ fn serialize(
+ &mut self,
+ buff: &mut DataWrite<'_>,
+ data: &Schematic,
+ ) -> Result<(), Self::WriteError> {
+ // write the header first just in case
+ buff.write_u32(SCHEMATIC_HEADER)?;
+ buff.write_u8(1)?;
+
+ let mut rbuff = DataWrite::new();
+ // don't have to check dimensions because they're already limited to MAX_DIMENSION
+ rbuff.write_i16(data.width as i16)?;
+ rbuff.write_i16(data.height as i16)?;
+ if data.tags.len() > u8::MAX as usize {
+ return Err(WriteError::TagCount(data.tags.len()));
+ }
+ rbuff.write_u8(data.tags.len() as u8)?;
+ for (k, v) in data.tags.iter() {
+ rbuff.write_utf(k)?;
+ rbuff.write_utf(v)?;
+ }
+ // use string keys here to avoid issues with different block refs with the same name
+ let mut block_map = HashMap::<&str, u32>::new();
+ let mut block_table = Vec::<&str>::new();
+ for curr in data.blocks.iter() {
+ match block_map.entry(curr.block.get_name()) {
+ Entry::Vacant(e) => {
+ e.insert(block_table.len() as u32);
+ block_table.push(curr.block.get_name());
+ }
+ _ => (),
+ }
+ }
+ if block_table.len() > i8::MAX as usize {
+ return Err(WriteError::TableSize(block_table.len()));
+ }
+ // else: implies contents are also valid i8 (they're strictly less than the map length)
+ rbuff.write_i8(block_table.len() as i8)?;
+ for &name in block_table.iter() {
+ rbuff.write_utf(name)?;
+ }
+ // don't have to check data.blocks.len() because dimensions don't allow exceeding MAX_BLOCKS
+ rbuff.write_i32(data.blocks.len() as i32)?;
+ let mut num = 0;
+ for curr in data.blocks.iter() {
+ rbuff.write_i8(block_map[curr.block.get_name()] as i8)?;
+ rbuff.write_u32(u32::from(curr.pos))?;
+ let data = match curr.state {
+ None => DynData::Empty,
+ Some(ref s) => curr.block.serialize_state(s.as_ref())?,
+ };
+ DynSerializer.serialize(&mut rbuff, &data)?;
+ rbuff.write_u8(curr.rot.into())?;
+ num += 1;
+ }
+ assert_eq!(num, data.blocks.len());
+
+ // compress into the provided buffer
+ let raw = match rbuff.data {
+ data::WriteBuff::Vec(v) => v,
+ _ => unreachable!("write buffer not owned"),
+ };
+ let mut comp = Compress::new(Compression::default(), true);
+ // compress the immediate buffer into a temp buffer to copy it to buff? no thanks
+ match buff.data {
+ data::WriteBuff::Ref {
+ raw: ref mut dst,
+ ref mut pos,
+ } => {
+ match comp.compress(&raw, &mut dst[*pos..], FlushCompress::Finish)? {
+ // there's no more input (and the flush mode says so), but we can't resize the output
+ Status::Ok | Status::BufError => {
+ return Err(WriteError::CompressEof(
+ raw.len() - comp.total_in() as usize,
+ ))
+ }
+ Status::StreamEnd => (),
+ }
+ }
+ data::WriteBuff::Vec(ref mut dst) => {
+ let mut input = raw.as_ref();
+ dst.reserve(1024);
+ loop {
+ let t_in = comp.total_in();
+ let t_out = comp.total_out();
+ let res = comp.compress_vec(input, dst, FlushCompress::Finish)?;
+ if comp.total_in() > t_in {
+ // we have to advance input every time, compress_vec only knows the output position
+ input = &input[(comp.total_in() - t_in) as usize..];
+ }
+ match res {
+ // there's no more input (and the flush mode says so), we need to reserve additional space
+ Status::Ok | Status::BufError => (),
+ // input was already at the end, so this is referring to the output
+ Status::StreamEnd => break,
+ }
+ if comp.total_in() == t_in && comp.total_out() == t_out {
+ // protect against looping forever
+ return Err(WriteError::CompressStall);
+ }
+ dst.reserve(1024);
+ }
+ }
+ }
+ assert_eq!(comp.total_in() as usize, raw.len());
+ Ok(())
+ }
}
#[derive(Debug)]
-pub enum ReadError
-{
- Read(data::ReadError),
- Header(u32),
- Version(u8),
- Decompress(DecompressError),
- DecompressStall,
- Dimensions(i16, i16),
- TableSize(i8),
- NoSuchBlock(String),
- BlockCount(i32),
- BlockIndex(i8, usize),
- BlockConfig(block::DataConvertError),
- ReadState(dynamic::ReadError),
- Placement(PlaceError),
+pub enum ReadError {
+ Read(data::ReadError),
+ Header(u32),
+ Version(u8),
+ Decompress(DecompressError),
+ DecompressStall,
+ Dimensions(i16, i16),
+ TableSize(i8),
+ NoSuchBlock(String),
+ BlockCount(i32),
+ BlockIndex(i8, usize),
+ BlockConfig(block::DataConvertError),
+ ReadState(dynamic::ReadError),
+ Placement(PlaceError),
}
-impl From<data::ReadError> for ReadError
-{
- fn from(value: data::ReadError) -> Self
- {
- Self::Read(value)
- }
+impl From<data::ReadError> for ReadError {
+ fn from(value: data::ReadError) -> Self {
+ Self::Read(value)
+ }
}
-impl From<DecompressError> for ReadError
-{
- fn from(value: DecompressError) -> Self
- {
- Self::Decompress(value)
- }
+impl From<DecompressError> for ReadError {
+ fn from(value: DecompressError) -> Self {
+ Self::Decompress(value)
+ }
}
-impl From<dynamic::ReadError> for ReadError
-{
- fn from(value: dynamic::ReadError) -> Self
- {
- Self::ReadState(value)
- }
+impl From<dynamic::ReadError> for ReadError {
+ fn from(value: dynamic::ReadError) -> Self {
+ Self::ReadState(value)
+ }
}
-impl From<block::DataConvertError> for ReadError
-{
- fn from(value: block::DataConvertError) -> Self
- {
- Self::BlockConfig(value)
- }
+impl From<block::DataConvertError> for ReadError {
+ fn from(value: block::DataConvertError) -> Self {
+ Self::BlockConfig(value)
+ }
}
-impl From<PlaceError> for ReadError
-{
- fn from(value: PlaceError) -> Self
- {
- Self::Placement(value)
- }
+impl From<PlaceError> for ReadError {
+ fn from(value: PlaceError) -> Self {
+ Self::Placement(value)
+ }
}
-impl fmt::Display for ReadError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Read(..) => f.write_str("failed to read from buffer"),
- Self::Header(hdr) => write!(f, "incorrect header ({hdr:08X})"),
- Self::Version(ver) => write!(f, "unsupported version ({ver})"),
- Self::Decompress(..) => f.write_str("zlib decompression failed"),
- Self::DecompressStall => f.write_str("decompressor stalled before completion"),
- Self::Dimensions(w, h) => write!(f, "invalid schematic dimensions ({w} * {h})"),
- Self::TableSize(cnt) => write!(f, "invalid block table size ({cnt})"),
- Self::NoSuchBlock(name) => write!(f, "unknown block {name:?}"),
- Self::BlockCount(cnt) => write!(f, "invalid total block count ({cnt})"),
- Self::BlockIndex(idx, cnt) => write!(f, "invalid block index ({idx} / {cnt})"),
- Self::BlockConfig(..) => f.write_str("block config conversion failed"),
- Self::ReadState(..) => f.write_str("failed to read block data"),
- Self::Placement(..) => f.write_str("deserialized block could not be placed"),
- }
- }
+impl fmt::Display for ReadError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Read(..) => f.write_str("failed to read from buffer"),
+ Self::Header(hdr) => write!(f, "incorrect header ({hdr:08X})"),
+ Self::Version(ver) => write!(f, "unsupported version ({ver})"),
+ Self::Decompress(..) => f.write_str("zlib decompression failed"),
+ Self::DecompressStall => f.write_str("decompressor stalled before completion"),
+ Self::Dimensions(w, h) => write!(f, "invalid schematic dimensions ({w} * {h})"),
+ Self::TableSize(cnt) => write!(f, "invalid block table size ({cnt})"),
+ Self::NoSuchBlock(name) => write!(f, "unknown block {name:?}"),
+ Self::BlockCount(cnt) => write!(f, "invalid total block count ({cnt})"),
+ Self::BlockIndex(idx, cnt) => write!(f, "invalid block index ({idx} / {cnt})"),
+ Self::BlockConfig(..) => f.write_str("block config conversion failed"),
+ Self::ReadState(..) => f.write_str("failed to read block data"),
+ Self::Placement(..) => f.write_str("deserialized block could not be placed"),
+ }
+ }
}
-impl Error for ReadError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::Read(e) => Some(e),
- Self::Decompress(e) => Some(e),
- Self::BlockConfig(e) => Some(e),
- Self::ReadState(e) => Some(e),
- Self::Placement(e) => Some(e),
- _ => None,
- }
- }
+impl Error for ReadError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Read(e) => Some(e),
+ Self::Decompress(e) => Some(e),
+ Self::BlockConfig(e) => Some(e),
+ Self::ReadState(e) => Some(e),
+ Self::Placement(e) => Some(e),
+ _ => None,
+ }
+ }
}
#[derive(Debug)]
-pub enum WriteError
-{
- Write(data::WriteError),
- TagCount(usize),
- TableSize(usize),
- StateSerialize(block::SerializeError),
- WriteState(dynamic::WriteError),
- Compress(CompressError),
- CompressEof(usize),
- CompressStall,
+pub enum WriteError {
+ Write(data::WriteError),
+ TagCount(usize),
+ TableSize(usize),
+ StateSerialize(block::SerializeError),
+ WriteState(dynamic::WriteError),
+ Compress(CompressError),
+ CompressEof(usize),
+ CompressStall,
}
-impl From<data::WriteError> for WriteError
-{
- fn from(value: data::WriteError) -> Self
- {
- Self::Write(value)
- }
+impl From<data::WriteError> for WriteError {
+ fn from(value: data::WriteError) -> Self {
+ Self::Write(value)
+ }
}
-impl From<block::SerializeError> for WriteError
-{
- fn from(value: block::SerializeError) -> Self
- {
- Self::StateSerialize(value)
- }
+impl From<block::SerializeError> for WriteError {
+ fn from(value: block::SerializeError) -> Self {
+ Self::StateSerialize(value)
+ }
}
-impl From<CompressError> for WriteError
-{
- fn from(value: CompressError) -> Self
- {
- Self::Compress(value)
- }
+impl From<CompressError> for WriteError {
+ fn from(value: CompressError) -> Self {
+ Self::Compress(value)
+ }
}
-impl From<dynamic::WriteError> for WriteError
-{
- fn from(value: dynamic::WriteError) -> Self
- {
- Self::WriteState(value)
- }
+impl From<dynamic::WriteError> for WriteError {
+ fn from(value: dynamic::WriteError) -> Self {
+ Self::WriteState(value)
+ }
}
-impl fmt::Display for WriteError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Write(..) => f.write_str("failed to write data to buffer"),
- Self::TagCount(len) => write!(f, "tag list too long ({len})"),
- Self::TableSize(len) => write!(f, "block table too long ({len})"),
- Self::StateSerialize(e) => e.fmt(f),
- Self::WriteState(..) => f.write_str("failed to write block data"),
- Self::Compress(..) => f.write_str("zlib compression failed"),
- Self::CompressEof(remain) => write!(f, "compression overflow with {remain} bytes of input remaining"),
- Self::CompressStall => f.write_str("compressor stalled before completion"),
- }
- }
+impl fmt::Display for WriteError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Write(..) => f.write_str("failed to write data to buffer"),
+ Self::TagCount(len) => write!(f, "tag list too long ({len})"),
+ Self::TableSize(len) => write!(f, "block table too long ({len})"),
+ Self::StateSerialize(e) => e.fmt(f),
+ Self::WriteState(..) => f.write_str("failed to write block data"),
+ Self::Compress(..) => f.write_str("zlib compression failed"),
+ Self::CompressEof(remain) => write!(
+ f,
+ "compression overflow with {remain} bytes of input remaining"
+ ),
+ Self::CompressStall => f.write_str("compressor stalled before completion"),
+ }
+ }
}
-impl Error for WriteError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::Write(e) => Some(e),
- Self::StateSerialize(e) => e.source(),
- Self::Compress(e) => Some(e),
- _ => None,
- }
- }
+impl Error for WriteError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Write(e) => Some(e),
+ Self::StateSerialize(e) => e.source(),
+ Self::Compress(e) => Some(e),
+ _ => None,
+ }
+ }
}
-impl<'l> SchematicSerializer<'l>
-{
- pub fn deserialize_base64(&mut self, data: &str) -> Result<Schematic<'l>, R64Error>
- {
- let mut buff = Vec::<u8>::new();
- buff.resize(data.len() / 4 * 3 + 1, 0);
- let n_out = base64::decode(data.as_bytes(), buff.as_mut())?;
- Ok(self.deserialize(&mut DataRead::new(&buff[..n_out]))?)
- }
-
- pub fn serialize_base64(&mut self, data: &Schematic<'l>) -> Result<String, W64Error>
- {
- let mut buff = DataWrite::new();
- self.serialize(&mut buff, data)?;
- let buff = buff.get_written();
- // round up because of padding
- let required = 4 * (buff.len() / 3 + if buff.len() % 3 != 0 {1} else {0});
- let mut text = Vec::<u8>::new();
- text.resize(required, 0);
- let n_out = base64::encode(buff, text.as_mut())?;
- // trailing zeros are valid UTF8, but not valid base64
- assert_eq!(n_out, text.len());
- // SAFETY: base64 encoding outputs pure ASCII (see base64::CHARS)
- Ok(unsafe{String::from_utf8_unchecked(text)})
- }
+impl<'l> SchematicSerializer<'l> {
+ pub fn deserialize_base64(&mut self, data: &str) -> Result<Schematic<'l>, R64Error> {
+ let mut buff = Vec::<u8>::new();
+ buff.resize(data.len() / 4 * 3 + 1, 0);
+ let n_out = base64::decode(data.as_bytes(), buff.as_mut())?;
+ Ok(self.deserialize(&mut DataRead::new(&buff[..n_out]))?)
+ }
+
+ pub fn serialize_base64(&mut self, data: &Schematic<'l>) -> Result<String, W64Error> {
+ let mut buff = DataWrite::new();
+ self.serialize(&mut buff, data)?;
+ let buff = buff.get_written();
+ // round up because of padding
+ let required = 4 * (buff.len() / 3 + if buff.len() % 3 != 0 { 1 } else { 0 });
+ let mut text = Vec::<u8>::new();
+ text.resize(required, 0);
+ let n_out = base64::encode(buff, text.as_mut())?;
+ // trailing zeros are valid UTF8, but not valid base64
+ assert_eq!(n_out, text.len());
+ // SAFETY: base64 encoding outputs pure ASCII (see base64::CHARS)
+ Ok(unsafe { String::from_utf8_unchecked(text) })
+ }
}
#[derive(Debug)]
-pub enum R64Error
-{
- Base64(base64::DecodeError),
- Content(ReadError),
+pub enum R64Error {
+ Base64(base64::DecodeError),
+ Content(ReadError),
}
-impl From<base64::DecodeError> for R64Error
-{
- fn from(value: base64::DecodeError) -> Self
- {
- Self::Base64(value)
- }
+impl From<base64::DecodeError> for R64Error {
+ fn from(value: base64::DecodeError) -> Self {
+ Self::Base64(value)
+ }
}
-impl From<ReadError> for R64Error
-{
- fn from(value: ReadError) -> Self
- {
- Self::Content(value)
- }
+impl From<ReadError> for R64Error {
+ fn from(value: ReadError) -> Self {
+ Self::Content(value)
+ }
}
-impl fmt::Display for R64Error
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Base64(..) => f.write_str("base-64 decoding failed"),
- Self::Content(e) => e.fmt(f),
- }
- }
+impl fmt::Display for R64Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Base64(..) => f.write_str("base-64 decoding failed"),
+ Self::Content(e) => e.fmt(f),
+ }
+ }
}
-impl Error for R64Error
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::Base64(e) => Some(e),
- Self::Content(e) => e.source(),
- }
- }
+impl Error for R64Error {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Base64(e) => Some(e),
+ Self::Content(e) => e.source(),
+ }
+ }
}
#[derive(Debug)]
-pub enum W64Error
-{
- Base64(base64::EncodeError),
- Content(WriteError),
+pub enum W64Error {
+ Base64(base64::EncodeError),
+ Content(WriteError),
}
-impl From<base64::EncodeError> for W64Error
-{
- fn from(value: base64::EncodeError) -> Self
- {
- Self::Base64(value)
- }
+impl From<base64::EncodeError> for W64Error {
+ fn from(value: base64::EncodeError) -> Self {
+ Self::Base64(value)
+ }
}
-impl From<WriteError> for W64Error
-{
- fn from(value: WriteError) -> Self
- {
- Self::Content(value)
- }
+impl From<WriteError> for W64Error {
+ fn from(value: WriteError) -> Self {
+ Self::Content(value)
+ }
}
-impl fmt::Display for W64Error
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Base64(..) => f.write_str("base-64 encoding failed"),
- Self::Content(e) => e.fmt(f),
- }
- }
+impl fmt::Display for W64Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Base64(..) => f.write_str("base-64 encoding failed"),
+ Self::Content(e) => e.fmt(f),
+ }
+ }
}
-impl Error for W64Error
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::Base64(e) => Some(e),
- Self::Content(e) => e.source(),
- }
- }
+impl Error for W64Error {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Base64(e) => Some(e),
+ Self::Content(e) => e.source(),
+ }
+ }
}
-pub struct PosIter
-{
- x: u16,
- y: u16,
- w: u16,
- h: u16,
+pub struct PosIter {
+ x: u16,
+ y: u16,
+ w: u16,
+ h: u16,
}
-impl Iterator for PosIter
-{
- type Item = GridPos;
-
- fn next(&mut self) -> Option<Self::Item>
- {
- if self.w > 0 && self.y < self.h
- {
- let p = GridPos(self.x, self.y);
- self.x += 1;
- if self.x == self.w
- {
- self.x = 0;
- self.y += 1;
- }
- Some(p)
- }
- else {None}
- }
-
- fn size_hint(&self) -> (usize, Option<usize>)
- {
- let pos = (self.x as usize) + (self.y as usize) * (self.w as usize);
- let end = (self.w as usize) * (self.h as usize);
- (end - pos, Some(end - pos))
- }
-
- fn count(self) -> usize
- {
- let pos = (self.x as usize) + (self.y as usize) * (self.w as usize);
- let end = (self.w as usize) * (self.h as usize);
- end - pos
- }
-
- fn last(self) -> Option<Self::Item>
- {
- // self.y < self.h implies self.h > 0
- if self.w > 0 && self.y < self.h
- {
- Some(GridPos(self.w - 1, self.h - 1))
- }
- else {None}
- }
+impl Iterator for PosIter {
+ type Item = GridPos;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.w > 0 && self.y < self.h {
+ let p = GridPos(self.x, self.y);
+ self.x += 1;
+ if self.x == self.w {
+ self.x = 0;
+ self.y += 1;
+ }
+ Some(p)
+ } else {
+ None
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ let pos = (self.x as usize) + (self.y as usize) * (self.w as usize);
+ let end = (self.w as usize) * (self.h as usize);
+ (end - pos, Some(end - pos))
+ }
+
+ fn count(self) -> usize {
+ let pos = (self.x as usize) + (self.y as usize) * (self.w as usize);
+ let end = (self.w as usize) * (self.h as usize);
+ end - pos
+ }
+
+ fn last(self) -> Option<Self::Item> {
+ // self.y < self.h implies self.h > 0
+ if self.w > 0 && self.y < self.h {
+ Some(GridPos(self.w - 1, self.h - 1))
+ } else {
+ None
+ }
+ }
}
impl FusedIterator for PosIter {}
#[cfg(test)]
-mod test
-{
- use super::*;
-
- macro_rules!test_iter
+mod test {
+ use super::*;
+
+ macro_rules!test_iter
{
($name:ident, $it:expr, $($val:expr),+) =>
{
@@ -1431,7 +1423,16 @@ mod test
assert_eq!($it.next(), $val);
};
}
-
- test_iter!(block_iter, Schematic::new(3, 4).pos_iter(), Some(GridPos(0, 0)), Some(GridPos(1, 0)), Some(GridPos(2, 0)),
- Some(GridPos(0, 1)), 7, Some(GridPos(2, 3)), None);
+
+ test_iter!(
+ block_iter,
+ Schematic::new(3, 4).pos_iter(),
+ Some(GridPos(0, 0)),
+ Some(GridPos(1, 0)),
+ Some(GridPos(2, 0)),
+ Some(GridPos(0, 1)),
+ 7,
+ Some(GridPos(2, 3)),
+ None
+ );
}
diff --git a/src/exe/args.rs b/src/exe/args.rs
index c3843b8..f037c12 100644
--- a/src/exe/args.rs
+++ b/src/exe/args.rs
@@ -4,512 +4,464 @@ use std::error;
use std::fmt;
use std::slice::from_ref;
-pub trait ArgHandler
-{
- type Error: error::Error + 'static;
-
- fn on_literal(&mut self, name: &str) -> Result<(), Self::Error>;
-
- fn on_short(&mut self, name: char, value: Option<&str>) -> Result<(), Self::Error>;
-
- fn on_long(&mut self, name: &str, value: Option<&str>) -> Result<(), Self::Error>;
+pub trait ArgHandler {
+ type Error: error::Error + 'static;
+
+ fn on_literal(&mut self, name: &str) -> Result<(), Self::Error>;
+
+ fn on_short(&mut self, name: char, value: Option<&str>) -> Result<(), Self::Error>;
+
+ fn on_long(&mut self, name: &str, value: Option<&str>) -> Result<(), Self::Error>;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum Error<E: error::Error + 'static>
-{
- Handler{pos: usize, val: E},
- EmptyName{pos: usize},
+pub enum Error<E: error::Error + 'static> {
+ Handler { pos: usize, val: E },
+ EmptyName { pos: usize },
}
-impl<E: error::Error + 'static> fmt::Display for Error<E>
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Handler{pos, val} => write!(f, "{val} (at #{pos})"),
- Self::EmptyName{pos} => write!(f, "malformed argument (at #{pos})"),
- }
- }
+impl<E: error::Error + 'static> fmt::Display for Error<E> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Handler { pos, val } => write!(f, "{val} (at #{pos})"),
+ Self::EmptyName { pos } => write!(f, "malformed argument (at #{pos})"),
+ }
+ }
}
-impl<E: error::Error + 'static> error::Error for Error<E>
-{
- fn source(&self) -> Option<&(dyn error::Error + 'static)>
- {
- match self
- {
- // forward past the inner error because we decorate it in our Display impl
- Self::Handler{val, ..} => val.source(),
- _ => None,
- }
- }
+impl<E: error::Error + 'static> error::Error for Error<E> {
+ fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+ match self {
+ // forward past the inner error because we decorate it in our Display impl
+ Self::Handler { val, .. } => val.source(),
+ _ => None,
+ }
+ }
}
-pub fn parse<I: Iterator, H: ArgHandler>(args: &mut I, handler: &mut H, arg_off: usize) -> Result<bool, Error<H::Error>>
- where I::Item: AsRef<str>
+pub fn parse<I: Iterator, H: ArgHandler>(
+ args: &mut I,
+ handler: &mut H,
+ arg_off: usize,
+) -> Result<bool, Error<H::Error>>
+where
+ I::Item: AsRef<str>,
{
- for (pos, arg) in args.enumerate()
- {
- let arg = arg.as_ref();
- if !arg.is_empty()
- {
- if arg.as_bytes()[0] == b'-'
- {
- if arg.len() >= 2 && arg.as_bytes()[1] == b'-'
- {
- if arg == "--" {return Ok(false);}
- let (name, value) = match arg.bytes().enumerate().find(|(_, b)| *b == b'=')
- {
- None => (&arg[2..], None),
- Some((i, _)) => (&arg[2..i], Some(&arg[i + 1..])),
- };
- if name.is_empty() {return Err(Error::EmptyName{pos: arg_off + pos});}
- if let Err(val) = handler.on_long(name, value)
- {
- return Err(Error::Handler{pos: arg_off + pos, val});
- }
- }
- else
- {
- let (value, end) = match arg.bytes().enumerate().find(|(_, b)| *b == b'=')
- {
- None => (None, arg.len()),
- Some((i, _)) => (Some(&arg[i + 1..]), i),
- };
- if end > 1
- {
- for c in arg[1..end].chars()
- {
- if let Err(val) = handler.on_short(c, value)
- {
- return Err(Error::Handler{pos: arg_off + pos, val});
- }
- }
- }
- else {return Err(Error::EmptyName{pos: arg_off + pos});}
- }
- }
- else
- {
- if let Err(val) = handler.on_literal(arg)
- {
- return Err(Error::Handler{pos: arg_off + pos, val});
- }
- }
- }
- }
- Ok(true)
+ for (pos, arg) in args.enumerate() {
+ let arg = arg.as_ref();
+ if !arg.is_empty() {
+ if arg.as_bytes()[0] == b'-' {
+ if arg.len() >= 2 && arg.as_bytes()[1] == b'-' {
+ if arg == "--" {
+ return Ok(false);
+ }
+ let (name, value) = match arg.bytes().enumerate().find(|(_, b)| *b == b'=') {
+ None => (&arg[2..], None),
+ Some((i, _)) => (&arg[2..i], Some(&arg[i + 1..])),
+ };
+ if name.is_empty() {
+ return Err(Error::EmptyName { pos: arg_off + pos });
+ }
+ if let Err(val) = handler.on_long(name, value) {
+ return Err(Error::Handler {
+ pos: arg_off + pos,
+ val,
+ });
+ }
+ } else {
+ let (value, end) = match arg.bytes().enumerate().find(|(_, b)| *b == b'=') {
+ None => (None, arg.len()),
+ Some((i, _)) => (Some(&arg[i + 1..]), i),
+ };
+ if end > 1 {
+ for c in arg[1..end].chars() {
+ if let Err(val) = handler.on_short(c, value) {
+ return Err(Error::Handler {
+ pos: arg_off + pos,
+ val,
+ });
+ }
+ }
+ } else {
+ return Err(Error::EmptyName { pos: arg_off + pos });
+ }
+ }
+ } else {
+ if let Err(val) = handler.on_literal(arg) {
+ return Err(Error::Handler {
+ pos: arg_off + pos,
+ val,
+ });
+ }
+ }
+ }
+ }
+ Ok(true)
}
-pub fn parse_args<H: ArgHandler>(handler: &mut H) -> Result<(), Error<H::Error>>
-{
- parse(&mut std::env::args(), handler, 0)?;
- Ok(())
+pub fn parse_args<H: ArgHandler>(handler: &mut H) -> Result<(), Error<H::Error>> {
+ parse(&mut std::env::args(), handler, 0)?;
+ Ok(())
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum ArgCount
-{
- Forbidden,
- Optional(usize),
- Required(usize),
+pub enum ArgCount {
+ Forbidden,
+ Optional(usize),
+ Required(usize),
}
-impl ArgCount
-{
- pub const fn has_value(&self) -> bool
- {
- match self
- {
- Self::Optional(..) | ArgCount::Required(..) => true,
- _ => false
- }
- }
-
- pub const fn is_required(&self) -> bool
- {
- match self
- {
- Self::Required(..) => true,
- _ => false
- }
- }
-
- pub const fn get_max_count(&self) -> Option<usize>
- {
- match self
- {
- Self::Optional(max) | ArgCount::Required(max) => Some(*max),
- _ => None,
- }
- }
+impl ArgCount {
+ pub const fn has_value(&self) -> bool {
+ match self {
+ Self::Optional(..) | ArgCount::Required(..) => true,
+ _ => false,
+ }
+ }
+
+ pub const fn is_required(&self) -> bool {
+ match self {
+ Self::Required(..) => true,
+ _ => false,
+ }
+ }
+
+ pub const fn get_max_count(&self) -> Option<usize> {
+ match self {
+ Self::Optional(max) | ArgCount::Required(max) => Some(*max),
+ _ => None,
+ }
+ }
}
#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct ArgOption
-{
- short: Option<char>,
- long: Option<Cow<'static, str>>,
- count: ArgCount,
+pub struct ArgOption {
+ short: Option<char>,
+ long: Option<Cow<'static, str>>,
+ count: ArgCount,
}
-impl ArgOption
-{
- pub const fn new(short: Option<char>, long: Option<Cow<'static, str>>, count: ArgCount) -> Self
- {
- if short.is_none() && long.is_none()
- {
- panic!("option must have at least a short or long name");
- }
- if let Some(max) = count.get_max_count()
- {
- if max == 0
- {
- panic!("argument must be allowed to appear at least once");
- }
- }
- Self{short, long, count}
- }
-
- pub fn get_short(&self) -> Option<char>
- {
- self.short
- }
-
- pub fn get_long(&self) -> Option<&str>
- {
- match self.long
- {
- None => None,
- Some(Cow::Borrowed(r)) => Some(r),
- Some(Cow::Owned(ref s)) => Some(s.as_str()),
- }
- }
-
- pub const fn get_count(&self) -> &ArgCount
- {
- &self.count
- }
+impl ArgOption {
+ pub const fn new(
+ short: Option<char>,
+ long: Option<Cow<'static, str>>,
+ count: ArgCount,
+ ) -> Self {
+ if short.is_none() && long.is_none() {
+ panic!("option must have at least a short or long name");
+ }
+ if let Some(max) = count.get_max_count() {
+ if max == 0 {
+ panic!("argument must be allowed to appear at least once");
+ }
+ }
+ Self { short, long, count }
+ }
+
+ pub fn get_short(&self) -> Option<char> {
+ self.short
+ }
+
+ pub fn get_long(&self) -> Option<&str> {
+ match self.long {
+ None => None,
+ Some(Cow::Borrowed(r)) => Some(r),
+ Some(Cow::Owned(ref s)) => Some(s.as_str()),
+ }
+ }
+
+ pub const fn get_count(&self) -> &ArgCount {
+ &self.count
+ }
}
-impl fmt::Display for ArgOption
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match (self.get_short(), self.get_long())
- {
- (None, None) => unreachable!("unnamed ArgOption"),
- (None, Some(long)) => write!(f, "\"--{long}\""),
- (Some(short), None) => write!(f, "\"-{short}\""),
- (Some(short), Some(long)) => write!(f, "\"--{long}\" / \"-{short}\""),
- }
- }
+impl fmt::Display for ArgOption {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match (self.get_short(), self.get_long()) {
+ (None, None) => unreachable!("unnamed ArgOption"),
+ (None, Some(long)) => write!(f, "\"--{long}\""),
+ (Some(short), None) => write!(f, "\"-{short}\""),
+ (Some(short), Some(long)) => write!(f, "\"--{long}\" / \"-{short}\""),
+ }
+ }
}
#[derive(Clone, Debug, Eq, PartialEq)]
-pub enum OptionValue
-{
- Absent,
- Present,
- Value(String),
- Values(Vec<String>),
+pub enum OptionValue {
+ Absent,
+ Present,
+ Value(String),
+ Values(Vec<String>),
}
-impl OptionValue
-{
- pub const fn is_absent(&self) -> bool
- {
- match self
- {
- Self::Absent => true,
- _ => false,
- }
- }
-
- pub const fn is_present(&self) -> bool
- {
- match self
- {
- Self::Present | Self::Value(..) | Self::Values(..) => true,
- _ => false,
- }
- }
-
- pub const fn has_value(&self) -> bool
- {
- match self
- {
- Self::Value(..) => true,
- Self::Values(..) => true,
- _ => false,
- }
- }
-
- pub const fn get_value(&self) -> Option<&String>
- {
- match self
- {
- Self::Value(v) => Some(v),
- _ => None,
- }
- }
-
- pub fn get_values(&self) -> Option<&[String]>
- {
- match self
- {
- Self::Value(v) => Some(from_ref(v)),
- Self::Values(v) => Some(v.as_ref()),
- _ => None,
- }
- }
+impl OptionValue {
+ pub const fn is_absent(&self) -> bool {
+ match self {
+ Self::Absent => true,
+ _ => false,
+ }
+ }
+
+ pub const fn is_present(&self) -> bool {
+ match self {
+ Self::Present | Self::Value(..) | Self::Values(..) => true,
+ _ => false,
+ }
+ }
+
+ pub const fn has_value(&self) -> bool {
+ match self {
+ Self::Value(..) => true,
+ Self::Values(..) => true,
+ _ => false,
+ }
+ }
+
+ pub const fn get_value(&self) -> Option<&String> {
+ match self {
+ Self::Value(v) => Some(v),
+ _ => None,
+ }
+ }
+
+ pub fn get_values(&self) -> Option<&[String]> {
+ match self {
+ Self::Value(v) => Some(from_ref(v)),
+ Self::Values(v) => Some(v.as_ref()),
+ _ => None,
+ }
+ }
}
#[derive(Clone, Debug)]
-pub struct OptionHandler
-{
- options: Vec<(ArgOption, OptionValue)>,
- short_map: HashMap<char, usize>,
- long_map: HashMap<String, usize>,
- literals: Vec<String>,
+pub struct OptionHandler {
+ options: Vec<(ArgOption, OptionValue)>,
+ short_map: HashMap<char, usize>,
+ long_map: HashMap<String, usize>,
+ literals: Vec<String>,
}
-impl OptionHandler
-{
- pub fn new() -> Self
- {
- Self{options: Vec::new(), short_map: HashMap::new(), long_map: HashMap::new(), literals: Vec::new()}
- }
-
- pub fn add(&mut self, opt: ArgOption) -> Result<OptionRef, AddArgError>
- {
- match opt.short
- {
- Some(c) => match self.short_map.get(&c)
- {
- Some(&i) => return Err(AddArgError{to_add: opt, existing: &self.options[i].0}),
- _ => (),
- },
- _ => (),
- }
- match opt.long
- {
- Some(ref s) => match self.long_map.get(&**s)
- {
- Some(&i) => return Err(AddArgError{to_add: opt, existing: &self.options[i].0}),
- _ => (),
- },
- _ => (),
- }
-
- let idx = self.options.len();
- self.options.push((opt, OptionValue::Absent));
- let opt = &self.options[idx].0;
- if let Some(c) = opt.short
- {
- self.short_map.insert(c, idx);
- }
- if let Some(ref s) = opt.long
- {
- let k = &**s;
- self.long_map.insert(k.to_owned(), idx);
- }
- Ok(OptionRef(idx))
- }
-
- pub fn options(&self) -> &Vec<(ArgOption, OptionValue)>
- {
- &self.options
- }
-
- pub fn get(&self, opt_ref: OptionRef) -> (&ArgOption, &OptionValue)
- {
- let opt = &self.options[opt_ref.0];
- (&opt.0, &opt.1)
- }
-
- pub fn get_option(&self, opt_ref: OptionRef) -> &ArgOption
- {
- &self.options[opt_ref.0].0
- }
-
- pub fn get_value(&self, opt_ref: OptionRef) -> &OptionValue
- {
- &self.options[opt_ref.0].1
- }
-
- pub fn get_short(&self, name: char) -> Option<&(ArgOption, OptionValue)>
- {
- self.short_map.get(&name).map(|&i| &self.options[i])
- }
-
- pub fn get_long(&self, name: &str) -> Option<&(ArgOption, OptionValue)>
- {
- self.long_map.get(name).map(|&i| &self.options[i])
- }
-
- pub fn get_literals(&self) -> &Vec<String>
- {
- &self.literals
- }
-
- fn set_arg(&mut self, idx: usize, value: Option<&str>) -> Result<(), OptionError>
- {
- let (ref o, ref mut curr) = self.options[idx];
- match o.count
- {
- ArgCount::Forbidden =>
- {
- if let None = value
- {
- if curr.is_absent() {*curr = OptionValue::Present;}
- Ok(())
- }
- else {Err(OptionError::ValueForbidden(o.clone()))}
- },
- ArgCount::Optional(max) =>
- {
- match curr
- {
- OptionValue::Absent | OptionValue::Present =>
- {
- if let Some(v) = value
- {
- if max == 1 {*curr = OptionValue::Value(v.to_owned());}
- else {*curr = OptionValue::Values(vec![v.to_owned()]);}
- }
- else {*curr = OptionValue::Present;}
- Ok(())
- },
- OptionValue::Value(..) => Err(OptionError::TooMany(o.clone())),
- OptionValue::Values(vec) =>
- {
- if vec.len() <= max
- {
- if let Some(v) = value
- {
- vec.push(v.to_owned());
- }
- Ok(())
- }
- else {Err(OptionError::TooMany(o.clone()))}
- },
- }
- },
- ArgCount::Required(max) =>
- {
- if let Some(v) = value
- {
- match curr
- {
- OptionValue::Absent =>
- {
- if max == 1 {*curr = OptionValue::Value(v.to_owned());}
- else {*curr = OptionValue::Values(vec![v.to_owned()]);}
- Ok(())
- },
- OptionValue::Present => unreachable!("argument missing required value"),
- OptionValue::Value(..) => Err(OptionError::TooMany(o.clone())),
- OptionValue::Values(vec) =>
- {
- if vec.len() <= max
- {
- vec.push(v.to_owned());
- Ok(())
- }
- else {Err(OptionError::TooMany(o.clone()))}
- },
- }
- }
- else {Err(OptionError::ValueRequired(o.clone()))}
- },
- }
- }
-
- pub fn clear(&mut self)
- {
- self.options.iter_mut().for_each(|(_, v)| *v = OptionValue::Absent);
- }
+impl OptionHandler {
+ pub fn new() -> Self {
+ Self {
+ options: Vec::new(),
+ short_map: HashMap::new(),
+ long_map: HashMap::new(),
+ literals: Vec::new(),
+ }
+ }
+
+ pub fn add(&mut self, opt: ArgOption) -> Result<OptionRef, AddArgError> {
+ match opt.short {
+ Some(c) => match self.short_map.get(&c) {
+ Some(&i) => {
+ return Err(AddArgError {
+ to_add: opt,
+ existing: &self.options[i].0,
+ })
+ }
+ _ => (),
+ },
+ _ => (),
+ }
+ match opt.long {
+ Some(ref s) => match self.long_map.get(&**s) {
+ Some(&i) => {
+ return Err(AddArgError {
+ to_add: opt,
+ existing: &self.options[i].0,
+ })
+ }
+ _ => (),
+ },
+ _ => (),
+ }
+
+ let idx = self.options.len();
+ self.options.push((opt, OptionValue::Absent));
+ let opt = &self.options[idx].0;
+ if let Some(c) = opt.short {
+ self.short_map.insert(c, idx);
+ }
+ if let Some(ref s) = opt.long {
+ let k = &**s;
+ self.long_map.insert(k.to_owned(), idx);
+ }
+ Ok(OptionRef(idx))
+ }
+
+ pub fn options(&self) -> &Vec<(ArgOption, OptionValue)> {
+ &self.options
+ }
+
+ pub fn get(&self, opt_ref: OptionRef) -> (&ArgOption, &OptionValue) {
+ let opt = &self.options[opt_ref.0];
+ (&opt.0, &opt.1)
+ }
+
+ pub fn get_option(&self, opt_ref: OptionRef) -> &ArgOption {
+ &self.options[opt_ref.0].0
+ }
+
+ pub fn get_value(&self, opt_ref: OptionRef) -> &OptionValue {
+ &self.options[opt_ref.0].1
+ }
+
+ pub fn get_short(&self, name: char) -> Option<&(ArgOption, OptionValue)> {
+ self.short_map.get(&name).map(|&i| &self.options[i])
+ }
+
+ pub fn get_long(&self, name: &str) -> Option<&(ArgOption, OptionValue)> {
+ self.long_map.get(name).map(|&i| &self.options[i])
+ }
+
+ pub fn get_literals(&self) -> &Vec<String> {
+ &self.literals
+ }
+
+ fn set_arg(&mut self, idx: usize, value: Option<&str>) -> Result<(), OptionError> {
+ let (ref o, ref mut curr) = self.options[idx];
+ match o.count {
+ ArgCount::Forbidden => {
+ if let None = value {
+ if curr.is_absent() {
+ *curr = OptionValue::Present;
+ }
+ Ok(())
+ } else {
+ Err(OptionError::ValueForbidden(o.clone()))
+ }
+ }
+ ArgCount::Optional(max) => match curr {
+ OptionValue::Absent | OptionValue::Present => {
+ if let Some(v) = value {
+ if max == 1 {
+ *curr = OptionValue::Value(v.to_owned());
+ } else {
+ *curr = OptionValue::Values(vec![v.to_owned()]);
+ }
+ } else {
+ *curr = OptionValue::Present;
+ }
+ Ok(())
+ }
+ OptionValue::Value(..) => Err(OptionError::TooMany(o.clone())),
+ OptionValue::Values(vec) => {
+ if vec.len() <= max {
+ if let Some(v) = value {
+ vec.push(v.to_owned());
+ }
+ Ok(())
+ } else {
+ Err(OptionError::TooMany(o.clone()))
+ }
+ }
+ },
+ ArgCount::Required(max) => {
+ if let Some(v) = value {
+ match curr {
+ OptionValue::Absent => {
+ if max == 1 {
+ *curr = OptionValue::Value(v.to_owned());
+ } else {
+ *curr = OptionValue::Values(vec![v.to_owned()]);
+ }
+ Ok(())
+ }
+ OptionValue::Present => unreachable!("argument missing required value"),
+ OptionValue::Value(..) => Err(OptionError::TooMany(o.clone())),
+ OptionValue::Values(vec) => {
+ if vec.len() <= max {
+ vec.push(v.to_owned());
+ Ok(())
+ } else {
+ Err(OptionError::TooMany(o.clone()))
+ }
+ }
+ }
+ } else {
+ Err(OptionError::ValueRequired(o.clone()))
+ }
+ }
+ }
+ }
+
+ pub fn clear(&mut self) {
+ self.options
+ .iter_mut()
+ .for_each(|(_, v)| *v = OptionValue::Absent);
+ }
}
#[derive(Clone, Copy, Debug)]
pub struct OptionRef(usize);
#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct AddArgError<'l>
-{
- pub to_add: ArgOption,
- pub existing: &'l ArgOption,
+pub struct AddArgError<'l> {
+ pub to_add: ArgOption,
+ pub existing: &'l ArgOption,
}
-impl<'l> fmt::Display for AddArgError<'l>
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- write!(f, "duplicate argument {} (already have {})", self.to_add, self.existing)
- }
+impl<'l> fmt::Display for AddArgError<'l> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "duplicate argument {} (already have {})",
+ self.to_add, self.existing
+ )
+ }
}
impl<'l> error::Error for AddArgError<'l> {}
-impl ArgHandler for OptionHandler
-{
- type Error = OptionError;
-
- fn on_literal(&mut self, name: &str) -> Result<(), Self::Error>
- {
- self.literals.push(name.to_owned());
- Ok(())
- }
-
- fn on_short(&mut self, name: char, value: Option<&str>) -> Result<(), Self::Error>
- {
- match self.short_map.get(&name)
- {
- None => Err(OptionError::NoSuchShort(name)),
- Some(&i) => self.set_arg(i, value),
- }
- }
-
- fn on_long(&mut self, name: &str, value: Option<&str>) -> Result<(), Self::Error>
- {
- match self.long_map.get(name)
- {
- None => Err(OptionError::NoSuchLong(name.to_owned())),
- Some(&i) => self.set_arg(i, value),
- }
- }
+impl ArgHandler for OptionHandler {
+ type Error = OptionError;
+
+ fn on_literal(&mut self, name: &str) -> Result<(), Self::Error> {
+ self.literals.push(name.to_owned());
+ Ok(())
+ }
+
+ fn on_short(&mut self, name: char, value: Option<&str>) -> Result<(), Self::Error> {
+ match self.short_map.get(&name) {
+ None => Err(OptionError::NoSuchShort(name)),
+ Some(&i) => self.set_arg(i, value),
+ }
+ }
+
+ fn on_long(&mut self, name: &str, value: Option<&str>) -> Result<(), Self::Error> {
+ match self.long_map.get(name) {
+ None => Err(OptionError::NoSuchLong(name.to_owned())),
+ Some(&i) => self.set_arg(i, value),
+ }
+ }
}
#[derive(Clone, Debug, Eq, PartialEq)]
-pub enum OptionError
-{
- NoSuchShort(char),
- NoSuchLong(String),
- ValueForbidden(ArgOption),
- ValueRequired(ArgOption),
- TooMany(ArgOption),
+pub enum OptionError {
+ NoSuchShort(char),
+ NoSuchLong(String),
+ ValueForbidden(ArgOption),
+ ValueRequired(ArgOption),
+ TooMany(ArgOption),
}
-impl fmt::Display for OptionError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::NoSuchShort(short) => write!(f, "invalid argument \"-{short}\""),
- Self::NoSuchLong(long) => write!(f, "invalid argument \"--{long}\""),
- Self::ValueForbidden(opt) => write!(f, "argument {opt} has no value"),
- Self::ValueRequired(opt) => write!(f, "argument {opt} requires a value"),
- Self::TooMany(opt) =>
- {
- if let Some(max) = opt.count.get_max_count() {write!(f, "too many {opt} (max {max})")}
- else {write!(f, "duplicate argument {opt}")}
- },
- }
- }
+impl fmt::Display for OptionError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::NoSuchShort(short) => write!(f, "invalid argument \"-{short}\""),
+ Self::NoSuchLong(long) => write!(f, "invalid argument \"--{long}\""),
+ Self::ValueForbidden(opt) => write!(f, "argument {opt} has no value"),
+ Self::ValueRequired(opt) => write!(f, "argument {opt} requires a value"),
+ Self::TooMany(opt) => {
+ if let Some(max) = opt.count.get_max_count() {
+ write!(f, "too many {opt} (max {max})")
+ } else {
+ write!(f, "duplicate argument {opt}")
+ }
+ }
+ }
+ }
}
impl error::Error for OptionError {}
diff --git a/src/exe/edit.rs b/src/exe/edit.rs
index 4b0ec94..d89e0b2 100644
--- a/src/exe/edit.rs
+++ b/src/exe/edit.rs
@@ -1,1466 +1,1490 @@
use std::borrow::Cow;
use std::env::Args;
-use std::io::{self, Write};
use std::fs;
+use std::io::{self, Write};
-use plandustry::block::{BlockRegistry, build_registry, Rotation};
+use plandustry::block::{build_registry, BlockRegistry, Rotation};
use plandustry::data::dynamic::DynData;
-use plandustry::data::{base64, DataRead, Serializer, DataWrite, GridPos};
use plandustry::data::schematic::{Placement, ResizeError, Schematic, SchematicSerializer};
+use plandustry::data::{base64, DataRead, DataWrite, GridPos, Serializer};
use plandustry::registry::RegistryEntry;
+use crate::args::{self, ArgCount, ArgOption, OptionHandler};
use crate::print::print_schematic;
use crate::print_err;
-use crate::args::{self, ArgCount, ArgOption, OptionHandler};
-struct State<'l>
-{
- reg: &'l BlockRegistry<'l>,
- schematic: Option<Schematic<'l>>,
- unsaved: bool,
- subregion: Option<Schematic<'l>>,
- quit: bool,
+struct State<'l> {
+ reg: &'l BlockRegistry<'l>,
+ schematic: Option<Schematic<'l>>,
+ unsaved: bool,
+ subregion: Option<Schematic<'l>>,
+ quit: bool,
}
-pub fn main(mut args: Args, arg_off: usize)
-{
- let mut handler = OptionHandler::new();
- let opt_file = handler.add(ArgOption::new(Some('f'), Some(Cow::Borrowed("file")), ArgCount::Required(1))).unwrap();
- if let Err(e) = args::parse(&mut args, &mut handler, arg_off)
- {
- print_err!(e, "Command error");
- return;
- }
-
- // try to load a schematic from the file argument or as base64
- let reg = build_registry();
- let mut ss = SchematicSerializer(&reg);
- let mut state = State{reg: &reg, schematic: None, unsaved: false, subregion: None, quit: false};
- if let Some(path) = handler.get_value(opt_file).get_value()
- {
- match fs::read(path)
- {
- Ok(data) =>
- {
- match ss.deserialize(&mut DataRead::new(&data))
- {
- Ok(s) =>
- {
- println!("Loaded schematic from {path}");
- state.schematic = Some(s);
- },
- Err(e) => print_err!(e, "Could not read schematic from {path}"),
- }
- },
- Err(e) => print_err!(e, "Could not read file {path:?}"),
- }
- }
- else if let Some(b64) = handler.get_literals().first()
- {
- match ss.deserialize_base64(b64)
- {
- Ok(s) =>
- {
- println!("Loaded schematic from CLI");
- state.schematic = Some(s);
- },
- Err(e) => print_err!(e, "Could not read schematic"),
- }
- }
- if state.schematic.is_none()
- {
- println!(r#"No active schematic, use "new" or "load" to begin editing."#);
- }
- println!(r#"Type "help" for a list of available commands."#);
-
- // the main command interpreter loop
- let mut line_buff = String::new();
- let stdin = io::stdin();
- while !state.quit
- {
- line_buff.clear();
- print!("> ");
- if let Err(e) = io::stdout().flush()
- {
- // what the print & println macros would do
- panic!("failed printing to stdout: {e}");
- }
- match stdin.read_line(&mut line_buff)
- {
- Ok(..) => interpret(&mut state, line_buff.trim_start()),
- Err(e) =>
- {
- print_err!(e, "Failed to read next command");
- if state.unsaved
- {
- // special case because we wouldn't be able to read a path from stdin
- match ss.serialize_base64(state.schematic.as_ref().unwrap())
- {
- Ok(curr) => println!("Current schematic: {curr}"),
- Err(e) => print_err!(e, "Could not serialize schematic"),
- }
- state.unsaved = false;
- }
- break;
- },
- }
- }
-
- // give the user a chance to save their work
- if state.unsaved
- {
- let mut data = DataWrite::new();
- match ss.serialize(&mut data, state.schematic.as_ref().unwrap())
- {
- Ok(()) =>
- {
- let data = data.get_written();
- // SAFETY: base64 output is always valid ASCII
- let buff = unsafe{line_buff.as_mut_vec()};
- buff.resize(4 * ((data.len() + 2) / 3), 0);
- match base64::encode(data, buff)
- {
- Ok(len) => println!("Current schematic: {}", &line_buff[..len]),
- Err(e) => print_err!(e, "Could not convert schematic to base-64"),
- }
-
- println!("You have unsaved work. Please type a path to save to or press enter to quit.");
- loop
- {
- line_buff.clear();
- match stdin.read_line(&mut line_buff)
- {
- Ok(..) =>
- {
- let path = line_buff.trim();
- if path.is_empty() {break;}
- match fs::write(path, data)
- {
- Ok(()) => println!("Saved schematic to {path}"),
- Err(e) => print_err!(e, "Could not write file {path:?}"),
- }
- },
- Err(e) =>
- {
- print_err!(e, "Failed to read save path");
- return;
- },
- }
- }
- },
- Err(e) => print_err!(e, "Could not serialize schematic"),
- }
- }
+pub fn main(mut args: Args, arg_off: usize) {
+ let mut handler = OptionHandler::new();
+ let opt_file = handler
+ .add(ArgOption::new(
+ Some('f'),
+ Some(Cow::Borrowed("file")),
+ ArgCount::Required(1),
+ ))
+ .unwrap();
+ if let Err(e) = args::parse(&mut args, &mut handler, arg_off) {
+ print_err!(e, "Command error");
+ return;
+ }
+
+ // try to load a schematic from the file argument or as base64
+ let reg = build_registry();
+ let mut ss = SchematicSerializer(&reg);
+ let mut state = State {
+ reg: &reg,
+ schematic: None,
+ unsaved: false,
+ subregion: None,
+ quit: false,
+ };
+ if let Some(path) = handler.get_value(opt_file).get_value() {
+ match fs::read(path) {
+ Ok(data) => match ss.deserialize(&mut DataRead::new(&data)) {
+ Ok(s) => {
+ println!("Loaded schematic from {path}");
+ state.schematic = Some(s);
+ }
+ Err(e) => print_err!(e, "Could not read schematic from {path}"),
+ },
+ Err(e) => print_err!(e, "Could not read file {path:?}"),
+ }
+ } else if let Some(b64) = handler.get_literals().first() {
+ match ss.deserialize_base64(b64) {
+ Ok(s) => {
+ println!("Loaded schematic from CLI");
+ state.schematic = Some(s);
+ }
+ Err(e) => print_err!(e, "Could not read schematic"),
+ }
+ }
+ if state.schematic.is_none() {
+ println!(r#"No active schematic, use "new" or "load" to begin editing."#);
+ }
+ println!(r#"Type "help" for a list of available commands."#);
+
+ // the main command interpreter loop
+ let mut line_buff = String::new();
+ let stdin = io::stdin();
+ while !state.quit {
+ line_buff.clear();
+ print!("> ");
+ if let Err(e) = io::stdout().flush() {
+ // what the print & println macros would do
+ panic!("failed printing to stdout: {e}");
+ }
+ match stdin.read_line(&mut line_buff) {
+ Ok(..) => interpret(&mut state, line_buff.trim_start()),
+ Err(e) => {
+ print_err!(e, "Failed to read next command");
+ if state.unsaved {
+ // special case because we wouldn't be able to read a path from stdin
+ match ss.serialize_base64(state.schematic.as_ref().unwrap()) {
+ Ok(curr) => println!("Current schematic: {curr}"),
+ Err(e) => print_err!(e, "Could not serialize schematic"),
+ }
+ state.unsaved = false;
+ }
+ break;
+ }
+ }
+ }
+
+ // give the user a chance to save their work
+ if state.unsaved {
+ let mut data = DataWrite::new();
+ match ss.serialize(&mut data, state.schematic.as_ref().unwrap()) {
+ Ok(()) => {
+ let data = data.get_written();
+ // SAFETY: base64 output is always valid ASCII
+ let buff = unsafe { line_buff.as_mut_vec() };
+ buff.resize(4 * ((data.len() + 2) / 3), 0);
+ match base64::encode(data, buff) {
+ Ok(len) => println!("Current schematic: {}", &line_buff[..len]),
+ Err(e) => print_err!(e, "Could not convert schematic to base-64"),
+ }
+
+ println!(
+ "You have unsaved work. Please type a path to save to or press enter to quit."
+ );
+ loop {
+ line_buff.clear();
+ match stdin.read_line(&mut line_buff) {
+ Ok(..) => {
+ let path = line_buff.trim();
+ if path.is_empty() {
+ break;
+ }
+ match fs::write(path, data) {
+ Ok(()) => println!("Saved schematic to {path}"),
+ Err(e) => print_err!(e, "Could not write file {path:?}"),
+ }
+ }
+ Err(e) => {
+ print_err!(e, "Failed to read save path");
+ return;
+ }
+ }
+ }
+ }
+ Err(e) => print_err!(e, "Could not serialize schematic"),
+ }
+ }
}
struct Tokenizer<'l>(Option<&'l str>);
-impl<'l> Tokenizer<'l>
-{
- fn skip_ws(&mut self)
- {
- if let Some(curr) = self.0
- {
- let curr = curr.trim_start();
- self.0 = if curr.is_empty() {None} else {Some(curr)};
- }
- }
-
- fn next(&mut self) -> Option<&'l str>
- {
- self.skip_ws();
- if let Some(curr) = self.0
- {
- if curr.len() >= 2 && (curr.as_bytes()[0] == b'"' || curr.as_bytes()[0] == b'\'')
- {
- match (&curr[1..]).find(curr.as_bytes()[0] as char)
- {
- None =>
- {
- self.0 = None;
- Some(&curr[1..])
- },
- Some(end) =>
- {
- let rest = &curr[(end + 2)..];
- self.0 = if rest.is_empty() {None} else {Some(rest)};
- Some(&curr[1..end])
- },
- }
- }
- else
- {
- match curr.find(char::is_whitespace)
- {
- None =>
- {
- self.0 = None;
- Some(curr)
- },
- Some(end) =>
- {
- let rest = &curr[end..];
- self.0 = if rest.is_empty() {None} else {Some(rest)};
- Some(&curr[..end])
- }
- }
- }
- }
- else {None}
- }
-
- fn remainder(&mut self) -> Option<&'l str>
- {
- self.skip_ws();
- if let Some(curr) = self.0
- {
- let bytes = curr.as_bytes();
- if bytes.len() >= 2 && (bytes[0] == b'"' || bytes[0] == b'\'') && bytes[bytes.len() - 1] == bytes[0]
- {
- self.0 = None;
- return Some(&curr[1..(curr.len() - 1)]);
- }
- }
- let curr = self.0;
- self.0 = None;
- curr
- }
+impl<'l> Tokenizer<'l> {
+ fn skip_ws(&mut self) {
+ if let Some(curr) = self.0 {
+ let curr = curr.trim_start();
+ self.0 = if curr.is_empty() { None } else { Some(curr) };
+ }
+ }
+
+ fn next(&mut self) -> Option<&'l str> {
+ self.skip_ws();
+ if let Some(curr) = self.0 {
+ if curr.len() >= 2 && (curr.as_bytes()[0] == b'"' || curr.as_bytes()[0] == b'\'') {
+ match (&curr[1..]).find(curr.as_bytes()[0] as char) {
+ None => {
+ self.0 = None;
+ Some(&curr[1..])
+ }
+ Some(end) => {
+ let rest = &curr[(end + 2)..];
+ self.0 = if rest.is_empty() { None } else { Some(rest) };
+ Some(&curr[1..end])
+ }
+ }
+ } else {
+ match curr.find(char::is_whitespace) {
+ None => {
+ self.0 = None;
+ Some(curr)
+ }
+ Some(end) => {
+ let rest = &curr[end..];
+ self.0 = if rest.is_empty() { None } else { Some(rest) };
+ Some(&curr[..end])
+ }
+ }
+ }
+ } else {
+ None
+ }
+ }
+
+ fn remainder(&mut self) -> Option<&'l str> {
+ self.skip_ws();
+ if let Some(curr) = self.0 {
+ let bytes = curr.as_bytes();
+ if bytes.len() >= 2
+ && (bytes[0] == b'"' || bytes[0] == b'\'')
+ && bytes[bytes.len() - 1] == bytes[0]
+ {
+ self.0 = None;
+ return Some(&curr[1..(curr.len() - 1)]);
+ }
+ }
+ let curr = self.0;
+ self.0 = None;
+ curr
+ }
}
-enum Command
-{
- Help, New, Input, Load, Place, Rotate, Mirror, Move, Resize, Remove, Sub, Print, Dump, Save, Quit
+enum Command {
+ Help,
+ New,
+ Input,
+ Load,
+ Place,
+ Rotate,
+ Mirror,
+ Move,
+ Resize,
+ Remove,
+ Sub,
+ Print,
+ Dump,
+ Save,
+ Quit,
}
-impl Command
-{
- fn print_help(&self, indent: usize)
- {
- match self
- {
- Self::Help => println!("{:<indent$}Prints a list of available commands", "\"help\":"),
- Self::New => println!("{:<indent$}Creates a new schematic, erasing the currently loaded one", "\"new\":"),
- Self::Input => println!("{:<indent$}Loads a new schematic from a base-64 encoded string", "\"input\":"),
- Self::Load => println!("{:<indent$}Loads a new schematic from a file", "\"load\":"),
- Self::Place => println!("{:<indent$}Places a block if enough space is available", "\"place\":"),
- Self::Rotate => println!("{:<indent$}Rotates the schematic (CCW) in increments of 90 degrees", "\"rotate\":"),
- Self::Mirror => println!("{:<indent$}Mirrors the schematic horizontally or vertically", "\"mirror\":"),
- Self::Move => println!("{:<indent$}Moves all blocks by a certain offset", "\"move\":"),
- Self::Resize => println!("{:<indent$}Resizes the schematic and offsets it", "\"resize\":"),
- Self::Remove => println!("{:<indent$}Removes blocks at a position or within a region", "\"remove\":"),
- Self::Sub => println!("{:<indent$}Various commands for editing subregions", "\"sub\":"),
- Self::Print => println!("{:<indent$}Prints the schematic in a visual representation", "\"print\":"),
- Self::Dump => println!("{:<indent$}Prints the schematic as a base-64 encoded string", "\"dump\":"),
- Self::Save => println!("{:<indent$}Saves the schematic to a file", "\"save\":"),
- Self::Quit => println!("{:<indent$}Offers to save unsaved work and exits the program", "\"quit\":"),
- }
- self.print_usage(indent);
- }
-
- fn print_usage(&self, indent: usize)
- {
- match self
- {
- Self::Help => (),
- Self::New => println!(r#"{:indent$} Usage: "new" <width> [<height>]"#, ""),
- Self::Input => println!(r#"{:indent$} Usage: "input" <base64>"#, ""),
- Self::Load => println!(r#"{:indent$} Usage: "load" <load path>"#, ""),
- Self::Place =>
- {
- println!(r#"{:indent$} Usage: "place" <x> <y> <block name> [<rotation> [<replace>]]"#, "");
- println!(r#"{:indent$} Rotation is one of right, up, left, down or compass angles"#, "")
- },
- Self::Rotate => println!(r#"{:indent$} Usage: "rotate" <angle>"#, ""),
- Self::Mirror => println!(r#"{:indent$} Usage: "mirror" <axis>"#, ""),
- Self::Move => println!(r#"{:indent$} Usage: "move" <dx> <dy>"#, ""),
- Self::Resize => println!(r#"{:indent$} Usage: "resize" <width> <height> [<dx> <dy>]"#, ""),
- Self::Remove => println!(r#"{:indent$} Usage: "remove" <x0> <y0> [<x1> <y1>]"#, ""),
- Self::Sub => println!(r#"{:indent$} Usage: "sub" ... (see "sub help")"#, ""),
- Self::Print | Self::Dump => (),
- Self::Save => println!(r#"{:indent$} Usage: "save" <save path>"#, ""),
- Self::Quit => (),
- }
- }
+impl Command {
+ fn print_help(&self, indent: usize) {
+ match self {
+ Self::Help => println!(
+ "{:<indent$}Prints a list of available commands",
+ "\"help\":"
+ ),
+ Self::New => println!(
+ "{:<indent$}Creates a new schematic, erasing the currently loaded one",
+ "\"new\":"
+ ),
+ Self::Input => println!(
+ "{:<indent$}Loads a new schematic from a base-64 encoded string",
+ "\"input\":"
+ ),
+ Self::Load => println!("{:<indent$}Loads a new schematic from a file", "\"load\":"),
+ Self::Place => println!(
+ "{:<indent$}Places a block if enough space is available",
+ "\"place\":"
+ ),
+ Self::Rotate => println!(
+ "{:<indent$}Rotates the schematic (CCW) in increments of 90 degrees",
+ "\"rotate\":"
+ ),
+ Self::Mirror => println!(
+ "{:<indent$}Mirrors the schematic horizontally or vertically",
+ "\"mirror\":"
+ ),
+ Self::Move => println!(
+ "{:<indent$}Moves all blocks by a certain offset",
+ "\"move\":"
+ ),
+ Self::Resize => println!(
+ "{:<indent$}Resizes the schematic and offsets it",
+ "\"resize\":"
+ ),
+ Self::Remove => println!(
+ "{:<indent$}Removes blocks at a position or within a region",
+ "\"remove\":"
+ ),
+ Self::Sub => println!(
+ "{:<indent$}Various commands for editing subregions",
+ "\"sub\":"
+ ),
+ Self::Print => println!(
+ "{:<indent$}Prints the schematic in a visual representation",
+ "\"print\":"
+ ),
+ Self::Dump => println!(
+ "{:<indent$}Prints the schematic as a base-64 encoded string",
+ "\"dump\":"
+ ),
+ Self::Save => println!("{:<indent$}Saves the schematic to a file", "\"save\":"),
+ Self::Quit => println!(
+ "{:<indent$}Offers to save unsaved work and exits the program",
+ "\"quit\":"
+ ),
+ }
+ self.print_usage(indent);
+ }
+
+ fn print_usage(&self, indent: usize) {
+ match self {
+ Self::Help => (),
+ Self::New => println!(r#"{:indent$} Usage: "new" <width> [<height>]"#, ""),
+ Self::Input => println!(r#"{:indent$} Usage: "input" <base64>"#, ""),
+ Self::Load => println!(r#"{:indent$} Usage: "load" <load path>"#, ""),
+ Self::Place => {
+ println!(
+ r#"{:indent$} Usage: "place" <x> <y> <block name> [<rotation> [<replace>]]"#,
+ ""
+ );
+ println!(
+ r#"{:indent$} Rotation is one of right, up, left, down or compass angles"#,
+ ""
+ )
+ }
+ Self::Rotate => println!(r#"{:indent$} Usage: "rotate" <angle>"#, ""),
+ Self::Mirror => println!(r#"{:indent$} Usage: "mirror" <axis>"#, ""),
+ Self::Move => println!(r#"{:indent$} Usage: "move" <dx> <dy>"#, ""),
+ Self::Resize => println!(
+ r#"{:indent$} Usage: "resize" <width> <height> [<dx> <dy>]"#,
+ ""
+ ),
+ Self::Remove => println!(r#"{:indent$} Usage: "remove" <x0> <y0> [<x1> <y1>]"#, ""),
+ Self::Sub => println!(r#"{:indent$} Usage: "sub" ... (see "sub help")"#, ""),
+ Self::Print | Self::Dump => (),
+ Self::Save => println!(r#"{:indent$} Usage: "save" <save path>"#, ""),
+ Self::Quit => (),
+ }
+ }
}
-macro_rules!parse_num
-{
- ($cmd:path, $name:expr, <$type:ty>::from($val:expr)) =>
- {
- match $val
- {
- None =>
- {
- eprintln!("Missing argument: {}", $name);
- $cmd.print_usage(0);
- return;
- },
- Some(s) =>
- {
- match <$type>::from_str_radix(s, 10)
- {
- Ok(v) => v,
- Err(e) =>
- {
- print_err!(e, "Could not parse {}", $name);
- $cmd.print_usage(0);
- return;
- },
- }
- },
- }
- };
- ($cmd:path, $tokens:expr, $name:expr, $type:ty) =>
- {
- parse_num!($cmd, $name, <$type>::from($tokens.next()))
- }
+macro_rules! parse_num {
+ ($cmd:path, $name:expr, <$type:ty>::from($val:expr)) => {
+ match $val {
+ None => {
+ eprintln!("Missing argument: {}", $name);
+ $cmd.print_usage(0);
+ return;
+ }
+ Some(s) => match <$type>::from_str_radix(s, 10) {
+ Ok(v) => v,
+ Err(e) => {
+ print_err!(e, "Could not parse {}", $name);
+ $cmd.print_usage(0);
+ return;
+ }
+ },
+ }
+ };
+ ($cmd:path, $tokens:expr, $name:expr, $type:ty) => {
+ parse_num!($cmd, $name, <$type>::from($tokens.next()))
+ };
}
-fn interpret(state: &mut State, cmd: &str)
-{
- let mut tokens = Tokenizer(Some(cmd));
- match tokens.next()
- {
- None => println!(r#"Empty command, type "quit" to exit"#),
- Some("help") =>
- {
- println!(r#"List of available commands:"#);
- const INDENT: usize = 12;
- Command::Help.print_help(INDENT);
- Command::New.print_help(INDENT);
- Command::Input.print_help(INDENT);
- Command::Load.print_help(INDENT);
- Command::Place.print_help(INDENT);
- Command::Rotate.print_help(INDENT);
- Command::Mirror.print_help(INDENT);
- Command::Remove.print_help(INDENT);
- Command::Sub.print_help(INDENT);
- Command::Print.print_help(INDENT);
- Command::Dump.print_help(INDENT);
- Command::Save.print_help(INDENT);
- Command::Quit.print_help(INDENT);
- println!();
- println!("Legend: \"literal (excluding quotes)\" <required> [<optional>]");
- println!("Arguments are delimited by whitespace, unless surrounded with ' or \"");
- if tokens.remainder().is_some()
- {
- eprintln!("Extra arguments are considered an error");
- }
- else
- {
- println!("Extra arguments are considered an error");
- }
- },
- Some("new") =>
- {
- let width = parse_num!(Command::New, tokens, "width", u16);
- if width == 0
- {
- eprintln!("Schematic width must be positive");
- return;
- }
- let height = parse_num!(Command::New, tokens, "height", u16);
- if height == 0
- {
- eprintln!("Schematic height must be positive");
- return;
- }
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "new""#);
- Command::New.print_usage(0);
- return;
- }
- state.schematic = Some(Schematic::new(width, height));
- // it's empty, no need to save this
- state.unsaved = false;
- },
- Some("input") =>
- {
- let schematic = match tokens.next()
- {
- None =>
- {
- eprintln!("Missing argument: base64");
- Command::Input.print_usage(0);
- return;
- },
- Some(b64) =>
- {
- match SchematicSerializer(state.reg).deserialize_base64(b64)
- {
- Ok(s) => s,
- Err(e) =>
- {
- print_err!(e, "Could not deserialize schematic");
- return;
- },
- }
- },
- };
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "input""#);
- Command::Input.print_usage(0);
- return;
- }
- state.schematic = Some(schematic);
- state.unsaved = false;
- },
- Some("load") =>
- {
- let schematic = match tokens.next()
- {
- None =>
- {
- eprintln!("Missing argument: load path");
- Command::Load.print_usage(0);
- return;
- },
- Some(path) =>
- {
- let data = match fs::read(path)
- {
- Ok(d) => d,
- Err(e) =>
- {
- print_err!(e, "Could not load from file");
- return;
- },
- };
- match SchematicSerializer(state.reg).deserialize(&mut DataRead::new(&data))
- {
- Ok(s) => s,
- Err(e) =>
- {
- print_err!(e, "Could not deserialize schematic");
- return;
- },
- }
- },
- };
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "load""#);
- Command::Load.print_usage(0);
- return;
- }
- state.schematic = Some(schematic);
- state.unsaved = false;
- },
- Some("place") =>
- {
- let Some(ref mut schematic) = state.schematic
+fn interpret(state: &mut State, cmd: &str) {
+ let mut tokens = Tokenizer(Some(cmd));
+ match tokens.next() {
+ None => println!(r#"Empty command, type "quit" to exit"#),
+ Some("help") => {
+ println!(r#"List of available commands:"#);
+ const INDENT: usize = 12;
+ Command::Help.print_help(INDENT);
+ Command::New.print_help(INDENT);
+ Command::Input.print_help(INDENT);
+ Command::Load.print_help(INDENT);
+ Command::Place.print_help(INDENT);
+ Command::Rotate.print_help(INDENT);
+ Command::Mirror.print_help(INDENT);
+ Command::Remove.print_help(INDENT);
+ Command::Sub.print_help(INDENT);
+ Command::Print.print_help(INDENT);
+ Command::Dump.print_help(INDENT);
+ Command::Save.print_help(INDENT);
+ Command::Quit.print_help(INDENT);
+ println!();
+ println!("Legend: \"literal (excluding quotes)\" <required> [<optional>]");
+ println!("Arguments are delimited by whitespace, unless surrounded with ' or \"");
+ if tokens.remainder().is_some() {
+ eprintln!("Extra arguments are considered an error");
+ } else {
+ println!("Extra arguments are considered an error");
+ }
+ }
+ Some("new") => {
+ let width = parse_num!(Command::New, tokens, "width", u16);
+ if width == 0 {
+ eprintln!("Schematic width must be positive");
+ return;
+ }
+ let height = parse_num!(Command::New, tokens, "height", u16);
+ if height == 0 {
+ eprintln!("Schematic height must be positive");
+ return;
+ }
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "new""#);
+ Command::New.print_usage(0);
+ return;
+ }
+ state.schematic = Some(Schematic::new(width, height));
+ // it's empty, no need to save this
+ state.unsaved = false;
+ }
+ Some("input") => {
+ let schematic = match tokens.next() {
+ None => {
+ eprintln!("Missing argument: base64");
+ Command::Input.print_usage(0);
+ return;
+ }
+ Some(b64) => match SchematicSerializer(state.reg).deserialize_base64(b64) {
+ Ok(s) => s,
+ Err(e) => {
+ print_err!(e, "Could not deserialize schematic");
+ return;
+ }
+ },
+ };
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "input""#);
+ Command::Input.print_usage(0);
+ return;
+ }
+ state.schematic = Some(schematic);
+ state.unsaved = false;
+ }
+ Some("load") => {
+ let schematic = match tokens.next() {
+ None => {
+ eprintln!("Missing argument: load path");
+ Command::Load.print_usage(0);
+ return;
+ }
+ Some(path) => {
+ let data = match fs::read(path) {
+ Ok(d) => d,
+ Err(e) => {
+ print_err!(e, "Could not load from file");
+ return;
+ }
+ };
+ match SchematicSerializer(state.reg).deserialize(&mut DataRead::new(&data)) {
+ Ok(s) => s,
+ Err(e) => {
+ print_err!(e, "Could not deserialize schematic");
+ return;
+ }
+ }
+ }
+ };
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "load""#);
+ Command::Load.print_usage(0);
+ return;
+ }
+ state.schematic = Some(schematic);
+ state.unsaved = false;
+ }
+ Some("place") => {
+ let Some(ref mut schematic) = state.schematic
else
{
eprintln!(r#"Command "place" requires an active schematic (see "help")"#);
return;
};
- let x = parse_num!(Command::Place, tokens, "x", u16);
- let y = parse_num!(Command::Place, tokens, "y", u16);
- if x >= schematic.get_width() || y >= schematic.get_height()
- {
- eprintln!("Invalid coordinate ({x} / {y}) out of bounds ({} / {})", schematic.get_width(), schematic.get_height());
- return;
- }
- let block = match tokens.next()
- {
- None =>
- {
- eprintln!("Missing argument: block name");
- Command::Place.print_usage(0);
- return;
- },
- Some(name) =>
- {
- match state.reg.get(name)
- {
- None =>
- {
- eprintln!("No such block {name:?}");
- return;
- },
- Some(b) => b,
- }
- },
- };
- let rot = match tokens.next()
- {
- None => None,
- Some("right") | Some("east") => Some(Rotation::Right),
- Some("up") | Some("north") => Some(Rotation::Up),
- Some("left") | Some("west") => Some(Rotation::Left),
- Some("down") | Some("south") => Some(Rotation::Down),
- Some(rot) =>
- {
- eprintln!("Invalid rotation {rot:?}");
- return;
- },
- };
- let replace = if rot.is_some()
- {
- match tokens.next()
- {
- None => None,
- Some("true") | Some("yes") => Some(true),
- Some("false") | Some("no") => Some(false),
- Some(replace) =>
- {
- eprintln!("Invalid replacement {replace:?}");
- return;
- },
- }
- }
- else {None};
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "place""#);
- Command::Place.print_usage(0);
- return;
- }
- let rot = rot.unwrap_or(Rotation::Right);
- let result = if replace.unwrap_or(false)
- {
- schematic.replace(x, y, block, DynData::Empty, rot, false).err()
- }
- else
- {
- schematic.set(x, y, block, DynData::Empty, rot).err()
- };
- if let Some(e) = result
- {
- print_err!(e, "Failed to place block at {x} / {y}");
- return;
- }
- },
- Some("rotate") =>
- {
- let Some(ref mut schematic) = state.schematic
+ let x = parse_num!(Command::Place, tokens, "x", u16);
+ let y = parse_num!(Command::Place, tokens, "y", u16);
+ if x >= schematic.get_width() || y >= schematic.get_height() {
+ eprintln!(
+ "Invalid coordinate ({x} / {y}) out of bounds ({} / {})",
+ schematic.get_width(),
+ schematic.get_height()
+ );
+ return;
+ }
+ let block = match tokens.next() {
+ None => {
+ eprintln!("Missing argument: block name");
+ Command::Place.print_usage(0);
+ return;
+ }
+ Some(name) => match state.reg.get(name) {
+ None => {
+ eprintln!("No such block {name:?}");
+ return;
+ }
+ Some(b) => b,
+ },
+ };
+ let rot = match tokens.next() {
+ None => None,
+ Some("right") | Some("east") => Some(Rotation::Right),
+ Some("up") | Some("north") => Some(Rotation::Up),
+ Some("left") | Some("west") => Some(Rotation::Left),
+ Some("down") | Some("south") => Some(Rotation::Down),
+ Some(rot) => {
+ eprintln!("Invalid rotation {rot:?}");
+ return;
+ }
+ };
+ let replace = if rot.is_some() {
+ match tokens.next() {
+ None => None,
+ Some("true") | Some("yes") => Some(true),
+ Some("false") | Some("no") => Some(false),
+ Some(replace) => {
+ eprintln!("Invalid replacement {replace:?}");
+ return;
+ }
+ }
+ } else {
+ None
+ };
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "place""#);
+ Command::Place.print_usage(0);
+ return;
+ }
+ let rot = rot.unwrap_or(Rotation::Right);
+ let result = if replace.unwrap_or(false) {
+ schematic
+ .replace(x, y, block, DynData::Empty, rot, false)
+ .err()
+ } else {
+ schematic.set(x, y, block, DynData::Empty, rot).err()
+ };
+ if let Some(e) = result {
+ print_err!(e, "Failed to place block at {x} / {y}");
+ return;
+ }
+ }
+ Some("rotate") => {
+ let Some(ref mut schematic) = state.schematic
else
{
eprintln!(r#"Command "rotate" requires an active schematic (see "help")"#);
return;
};
- let angle = parse_num!(Command::Rotate, tokens, "angle", i32);
- if angle % 90 != 0
- {
- eprintln!("Rotation angle must be a multiple of 90 degrees");
- return;
- }
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "rotate""#);
- Command::Rotate.print_usage(0);
- return;
- }
- match (angle / 90) % 4
- {
- 0 => (),
- 1 | -3 =>
- {
- schematic.rotate(false);
- state.unsaved = true;
- },
- 2 | -2 =>
- {
- schematic.rotate_180();
- state.unsaved = true;
- },
- 3 | -1 =>
- {
- schematic.rotate(true);
- state.unsaved = true;
- },
- a => unreachable!("angle {angle} -> {a}"),
- }
- },
- Some("mirror") =>
- {
- let Some(ref mut schematic) = state.schematic
+ let angle = parse_num!(Command::Rotate, tokens, "angle", i32);
+ if angle % 90 != 0 {
+ eprintln!("Rotation angle must be a multiple of 90 degrees");
+ return;
+ }
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "rotate""#);
+ Command::Rotate.print_usage(0);
+ return;
+ }
+ match (angle / 90) % 4 {
+ 0 => (),
+ 1 | -3 => {
+ schematic.rotate(false);
+ state.unsaved = true;
+ }
+ 2 | -2 => {
+ schematic.rotate_180();
+ state.unsaved = true;
+ }
+ 3 | -1 => {
+ schematic.rotate(true);
+ state.unsaved = true;
+ }
+ a => unreachable!("angle {angle} -> {a}"),
+ }
+ }
+ Some("mirror") => {
+ let Some(ref mut schematic) = state.schematic
else
{
eprintln!(r#"Command "mirror" requires an active schematic (see "help")"#);
return;
};
- let (x, y) = match tokens.next()
- {
- None =>
- {
- eprintln!("Missing argument: axis");
- Command::Mirror.print_usage(0);
- return;
- },
- Some("x") | Some("h") | Some("horizontal") | Some("horizontally") => (true, false),
- Some("y") | Some("v") | Some("vertical") | Some("vertically") => (false, true),
- Some("both") | Some("all") => (true, true),
- Some(axis) =>
- {
- eprintln!("Invalid mirroring axis: {axis:?}");
- return;
- },
- };
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "mirror""#);
- Command::Mirror.print_usage(0);
- return;
- }
- schematic.mirror(x, y);
- state.unsaved = true;
- },
- Some("move") =>
- {
- let Some(ref mut schematic) = state.schematic
+ let (x, y) = match tokens.next() {
+ None => {
+ eprintln!("Missing argument: axis");
+ Command::Mirror.print_usage(0);
+ return;
+ }
+ Some("x") | Some("h") | Some("horizontal") | Some("horizontally") => (true, false),
+ Some("y") | Some("v") | Some("vertical") | Some("vertically") => (false, true),
+ Some("both") | Some("all") => (true, true),
+ Some(axis) => {
+ eprintln!("Invalid mirroring axis: {axis:?}");
+ return;
+ }
+ };
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "mirror""#);
+ Command::Mirror.print_usage(0);
+ return;
+ }
+ schematic.mirror(x, y);
+ state.unsaved = true;
+ }
+ Some("move") => {
+ let Some(ref mut schematic) = state.schematic
else
{
eprintln!(r#"Command "move" requires an active schematic (see "help")"#);
return;
};
- let dx = parse_num!(Command::Move, tokens, "dx", i16);
- let dy = parse_num!(Command::Move, tokens, "dy", i16);
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "move""#);
- Command::Move.print_usage(0);
- return;
- }
- if dx != 0 && dy != 0
- {
- if let Err(e) = schematic.resize(dx, dy, schematic.get_width(), schematic.get_height())
- {
- match e
- {
- ResizeError::XOffset{dx, old_w, new_w} =>
- {
- debug_assert_eq!(old_w, new_w);
- eprintln!("Invalid horizontal move {dx} not in ]-{old_w}, {new_w}[");
- },
- ResizeError::YOffset{dy, old_h, new_h} =>
- {
- debug_assert_eq!(old_h, new_h);
- eprintln!("Invalid vertical move {dy} not in ]-{old_h}, {new_h}[");
- },
- ResizeError::Truncated{right, top, left, bottom} =>
- {
- eprint!("Move would truncate schematic: ");
- let mut first = true;
- if right > 0
- {
- eprint!("{}right: {right}", if first {""} else {", "});
- first = false;
- }
- if top > 0
- {
- eprint!("{}top: {top}", if first {""} else {", "});
- first = false;
- }
- if left > 0
- {
- eprint!("{}left: {left}", if first {""} else {", "});
- first = false;
- }
- if bottom > 0
- {
- eprint!("{}bottom: {bottom}", if first {""} else {", "});
- first = false;
- }
- if first {eprintln!("<unknown>");} else {eprintln!();}
- },
- _ => print_err!(e, "Unexpected resize error (for {dx} / {dy})")
- }
- }
- else {state.unsaved = true;}
- }
- },
- Some("resize") =>
- {
- let Some(ref mut schematic) = state.schematic
+ let dx = parse_num!(Command::Move, tokens, "dx", i16);
+ let dy = parse_num!(Command::Move, tokens, "dy", i16);
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "move""#);
+ Command::Move.print_usage(0);
+ return;
+ }
+ if dx != 0 && dy != 0 {
+ if let Err(e) =
+ schematic.resize(dx, dy, schematic.get_width(), schematic.get_height())
+ {
+ match e {
+ ResizeError::XOffset { dx, old_w, new_w } => {
+ debug_assert_eq!(old_w, new_w);
+ eprintln!("Invalid horizontal move {dx} not in ]-{old_w}, {new_w}[");
+ }
+ ResizeError::YOffset { dy, old_h, new_h } => {
+ debug_assert_eq!(old_h, new_h);
+ eprintln!("Invalid vertical move {dy} not in ]-{old_h}, {new_h}[");
+ }
+ ResizeError::Truncated {
+ right,
+ top,
+ left,
+ bottom,
+ } => {
+ eprint!("Move would truncate schematic: ");
+ let mut first = true;
+ if right > 0 {
+ eprint!("{}right: {right}", if first { "" } else { ", " });
+ first = false;
+ }
+ if top > 0 {
+ eprint!("{}top: {top}", if first { "" } else { ", " });
+ first = false;
+ }
+ if left > 0 {
+ eprint!("{}left: {left}", if first { "" } else { ", " });
+ first = false;
+ }
+ if bottom > 0 {
+ eprint!("{}bottom: {bottom}", if first { "" } else { ", " });
+ first = false;
+ }
+ if first {
+ eprintln!("<unknown>");
+ } else {
+ eprintln!();
+ }
+ }
+ _ => print_err!(e, "Unexpected resize error (for {dx} / {dy})"),
+ }
+ } else {
+ state.unsaved = true;
+ }
+ }
+ }
+ Some("resize") => {
+ let Some(ref mut schematic) = state.schematic
else
{
eprintln!(r#"Command "resize" requires an active schematic (see "help")"#);
return;
};
- let w = parse_num!(Command::Resize, tokens, "width", u16);
- if w == 0
- {
- eprintln!("Schematic width must be positive");
- return;
- }
- let h = parse_num!(Command::Resize, tokens, "height", u16);
- if h == 0
- {
- eprintln!("Schematic height must be positive");
- return;
- }
- let (dx, dy) = if let arg @ Some(..) = tokens.next()
- {
- let dx = parse_num!(Command::Resize, "dx", <i16>::from(arg));
- let dy = parse_num!(Command::Resize, tokens, "dy", i16);
- (dx, dy)
- }
- else {(0, 0)};
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "resize""#);
- Command::Resize.print_usage(0);
- return;
- }
- if w != schematic.get_width() || h != schematic.get_height() || dx != 0 || dy != 0
- {
- if let Err(e) = schematic.resize(dx, dy, w, h)
- {
- print_err!(e, "Could not resize schematic");
- }
- }
- },
- Some("remove") =>
- {
- let Some(ref mut schematic) = state.schematic
+ let w = parse_num!(Command::Resize, tokens, "width", u16);
+ if w == 0 {
+ eprintln!("Schematic width must be positive");
+ return;
+ }
+ let h = parse_num!(Command::Resize, tokens, "height", u16);
+ if h == 0 {
+ eprintln!("Schematic height must be positive");
+ return;
+ }
+ let (dx, dy) = if let arg @ Some(..) = tokens.next() {
+ let dx = parse_num!(Command::Resize, "dx", <i16>::from(arg));
+ let dy = parse_num!(Command::Resize, tokens, "dy", i16);
+ (dx, dy)
+ } else {
+ (0, 0)
+ };
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "resize""#);
+ Command::Resize.print_usage(0);
+ return;
+ }
+ if w != schematic.get_width() || h != schematic.get_height() || dx != 0 || dy != 0 {
+ if let Err(e) = schematic.resize(dx, dy, w, h) {
+ print_err!(e, "Could not resize schematic");
+ }
+ }
+ }
+ Some("remove") => {
+ let Some(ref mut schematic) = state.schematic
else
{
eprintln!(r#"Command "remove" requires an active schematic (see "help")"#);
return;
};
- let x0 = parse_num!(Command::Remove, tokens, "x0", u16);
- let y0 = parse_num!(Command::Remove, tokens, "y0", u16);
- if x0 >= schematic.get_width() || y0 >= schematic.get_height()
- {
- eprintln!("Invalid coordinate ({x0} / {y0}) out of bounds ({} / {})", schematic.get_width(), schematic.get_height());
- return;
- }
- let (x0, y0, x1, y1) = if let arg @ Some(..) = tokens.next()
- {
- let x1 = parse_num!(Command::Remove, "x1", <u16>::from(arg));
- let y1 = parse_num!(Command::Remove, tokens, "y1", u16);
- if x1 >= schematic.get_width() || y1 >= schematic.get_height()
- {
- eprintln!("Invalid coordinate ({x1} / {y1}) out of bounds ({} / {})", schematic.get_width(), schematic.get_height());
- return;
- }
- (x0.min(x1), y0.min(y1), x0.max(x1), y0.max(y1))
- }
- else {(x0, y0, x0, y0)};
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "remove""#);
- Command::Remove.print_usage(0);
- return;
- }
- if x1 > x0 || y1 > y0
- {
- let mut cnt = 0u32;
- for y in y0..=y1
- {
- for x in x0..=x1
- {
- // position was already checked while parsing
- if schematic.take(x, y).unwrap().is_some() {cnt += 1;}
- }
- }
- println!("Removed {cnt} blocks in {x0} / {y0} to {x1} / {y1}");
- if cnt > 0 {state.unsaved = true;}
- }
- else
- {
- // position was already checked while parsing
- match schematic.take(x0, y0).unwrap()
- {
- None => (),
- Some(p) =>
- {
- println!("Removed block {} from {x0} / {y0}", p.get_block().get_name());
- state.unsaved = true;
- },
- }
- }
- },
- Some("sub") => interpret_sub(state, &mut tokens),
- Some("print") =>
- {
- let Some(ref schematic) = state.schematic
+ let x0 = parse_num!(Command::Remove, tokens, "x0", u16);
+ let y0 = parse_num!(Command::Remove, tokens, "y0", u16);
+ if x0 >= schematic.get_width() || y0 >= schematic.get_height() {
+ eprintln!(
+ "Invalid coordinate ({x0} / {y0}) out of bounds ({} / {})",
+ schematic.get_width(),
+ schematic.get_height()
+ );
+ return;
+ }
+ let (x0, y0, x1, y1) = if let arg @ Some(..) = tokens.next() {
+ let x1 = parse_num!(Command::Remove, "x1", <u16>::from(arg));
+ let y1 = parse_num!(Command::Remove, tokens, "y1", u16);
+ if x1 >= schematic.get_width() || y1 >= schematic.get_height() {
+ eprintln!(
+ "Invalid coordinate ({x1} / {y1}) out of bounds ({} / {})",
+ schematic.get_width(),
+ schematic.get_height()
+ );
+ return;
+ }
+ (x0.min(x1), y0.min(y1), x0.max(x1), y0.max(y1))
+ } else {
+ (x0, y0, x0, y0)
+ };
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "remove""#);
+ Command::Remove.print_usage(0);
+ return;
+ }
+ if x1 > x0 || y1 > y0 {
+ let mut cnt = 0u32;
+ for y in y0..=y1 {
+ for x in x0..=x1 {
+ // position was already checked while parsing
+ if schematic.take(x, y).unwrap().is_some() {
+ cnt += 1;
+ }
+ }
+ }
+ println!("Removed {cnt} blocks in {x0} / {y0} to {x1} / {y1}");
+ if cnt > 0 {
+ state.unsaved = true;
+ }
+ } else {
+ // position was already checked while parsing
+ match schematic.take(x0, y0).unwrap() {
+ None => (),
+ Some(p) => {
+ println!(
+ "Removed block {} from {x0} / {y0}",
+ p.get_block().get_name()
+ );
+ state.unsaved = true;
+ }
+ }
+ }
+ }
+ Some("sub") => interpret_sub(state, &mut tokens),
+ Some("print") => {
+ let Some(ref schematic) = state.schematic
else
{
eprintln!(r#"Command "print" requires an active schematic (see "help")"#);
return;
};
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "print""#);
- Command::Print.print_usage(0);
- return;
- }
- print_schematic(schematic);
- },
- Some("dump") =>
- {
- let Some(ref schematic) = state.schematic
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "print""#);
+ Command::Print.print_usage(0);
+ return;
+ }
+ print_schematic(schematic);
+ }
+ Some("dump") => {
+ let Some(ref schematic) = state.schematic
else
{
eprintln!(r#"Command "dump" requires an active schematic (see "help")"#);
return;
};
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "dump""#);
- Command::Dump.print_usage(0);
- return;
- }
- let b64 = match SchematicSerializer(state.reg).serialize_base64(schematic)
- {
- Ok(b64) => b64,
- Err(e) =>
- {
- print_err!(e, "Could not serialize schematic");
- return;
- },
- };
- println!("Schematic: {}", b64);
- },
- Some("save") =>
- {
- let Some(ref schematic) = state.schematic
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "dump""#);
+ Command::Dump.print_usage(0);
+ return;
+ }
+ let b64 = match SchematicSerializer(state.reg).serialize_base64(schematic) {
+ Ok(b64) => b64,
+ Err(e) => {
+ print_err!(e, "Could not serialize schematic");
+ return;
+ }
+ };
+ println!("Schematic: {}", b64);
+ }
+ Some("save") => {
+ let Some(ref schematic) = state.schematic
else
{
eprintln!(r#"Command "save" requires an active schematic (see "help")"#);
return;
};
- let Some(path) = tokens.next()
+ let Some(path) = tokens.next()
else
{
eprintln!("Missing argument: save path");
Command::Save.print_usage(0);
return;
};
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "save""#);
- Command::Save.print_usage(0);
- return;
- }
- let mut serial_buff = DataWrite::new();
- if let Err(e) = SchematicSerializer(state.reg).serialize(&mut serial_buff, schematic)
- {
- print_err!(e, "Could not serialize schematic");
- return;
- }
- if let Err(e) = fs::write(path, serial_buff.get_written())
- {
- print_err!(e, "Could not write to file");
- return;
- }
- state.unsaved = false;
- println!("Saved schematic to {path}.");
- },
- Some("quit") => state.quit = true,
- Some(unknown) => eprintln!("Unknown command {unknown:?}"),
- }
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "save""#);
+ Command::Save.print_usage(0);
+ return;
+ }
+ let mut serial_buff = DataWrite::new();
+ if let Err(e) = SchematicSerializer(state.reg).serialize(&mut serial_buff, schematic) {
+ print_err!(e, "Could not serialize schematic");
+ return;
+ }
+ if let Err(e) = fs::write(path, serial_buff.get_written()) {
+ print_err!(e, "Could not write to file");
+ return;
+ }
+ state.unsaved = false;
+ println!("Saved schematic to {path}.");
+ }
+ Some("quit") => state.quit = true,
+ Some(unknown) => eprintln!("Unknown command {unknown:?}"),
+ }
}
-enum SubCommand
-{
- Help, Input, Copy, Cut, Paste, Place, Rotate, Mirror, Move, Resize, Remove, Print, Dump
+enum SubCommand {
+ Help,
+ Input,
+ Copy,
+ Cut,
+ Paste,
+ Place,
+ Rotate,
+ Mirror,
+ Move,
+ Resize,
+ Remove,
+ Print,
+ Dump,
}
-impl SubCommand
-{
- fn print_help(&self, indent: usize)
- {
- match self
- {
- Self::Help => println!("{:<indent$}Prints a list of available commands", "\"sub\" \"help\":"),
- Self::Input => println!("{:<indent$}Loads a new subregion from a base-64 encoded string", "\"sub\" \"input\":"),
- Self::Copy => println!("{:<indent$}Replaces the current subregion by copying from the schematic", "\"sub\" \"copy\":"),
- Self::Cut => println!("{:<indent$}Replaces the current subregion by removing from the schematic", "\"sub\" \"cut\":"),
- Self::Paste => println!("{:<indent$}Places a copy of the current subregion into the schematic", "\"sub\" \"paste\":"),
- Self::Place => println!("{:<indent$}Places a block if enough space is available", "\"sub\" \"place\":"),
- Self::Rotate => println!("{:<indent$}Rotates the current subregion (CCW) in increments of 90 degrees", "\"sub\" \"rotate\":"),
- Self::Mirror => println!("{:<indent$}Mirrors the current subregion horizontally or vertically", "\"sub\" \"mirror\":"),
- Self::Move => println!("{:<indent$}Moves all blocks by a certain offset", "\"sub\" \"move\":"),
- Self::Resize => println!("{:<indent$}Resizes the current subregion and offsets it", "\"sub\" \"resize\":"),
- Self::Remove => println!("{:<indent$}Removes blocks at a position or within a region", "\"sub\" \"remove\":"),
- Self::Print => println!("{:<indent$}Prints the current subregion in a visual representation", "\"sub\" \"print\":"),
- Self::Dump => println!("{:<indent$}Prints the current subregion as a base-64 encoded string", "\"sub\" \"dump\":"),
- }
- self.print_usage(indent);
- }
-
- fn print_usage(&self, indent: usize)
- {
- match self
- {
- Self::Help => (),
- Self::Input => println!(r#"{:indent$} Usage: "sub" "input" <base64>"#, ""),
- Self::Copy =>
- {
- println!(r#"{:indent$} Usage: "sub" "copy" <x0> <y0> <x1> <y1> [<strict>]"#, "");
- println!(r#"{:indent$} Strictness ignores blocks which are not entirely within the bounds"#, "");
- },
- Self::Cut =>
- {
- println!(r#"{:indent$} Usage: "sub" "cut" <x0> <y0> <x1> <y1> [<strict>]"#, "");
- println!(r#"{:indent$} Strictness ignores blocks which are not entirely within the bounds"#, "");
- },
- Self::Paste => println!(r#"{:indent$} Usage: "sub" "paste" <x> <y>"#, ""),
- Self::Place =>
- {
- println!(r#"{:indent$} Usage: "sub" "place" <x> <y> <block name> [<rotation> [<replace>]]"#, "");
- println!(r#"{:indent$} Rotation is one of right, up, left, down or compass angles"#, "")
- },
- Self::Rotate => println!(r#"{:indent$} Usage: "sub" "rotate" <angle>"#, ""),
- Self::Mirror => println!(r#"{:indent$} Usage: "sub" "mirror" <axis>"#, ""),
- Self::Move => println!(r#"{:indent$} Usage: "sub" "move" <dx> <dy>"#, ""),
- Self::Resize => println!(r#"{:indent$} Usage: "sub" "resize" <width> <height> [<dx> <dy>]"#, ""),
- Self::Remove => println!(r#"{:indent$} Usage: "sub" "remove" <x0> <y0> [<x1> <y1>]"#, ""),
- Self::Print | Self::Dump => (),
- }
- }
+impl SubCommand {
+ fn print_help(&self, indent: usize) {
+ match self {
+ Self::Help => println!(
+ "{:<indent$}Prints a list of available commands",
+ "\"sub\" \"help\":"
+ ),
+ Self::Input => println!(
+ "{:<indent$}Loads a new subregion from a base-64 encoded string",
+ "\"sub\" \"input\":"
+ ),
+ Self::Copy => println!(
+ "{:<indent$}Replaces the current subregion by copying from the schematic",
+ "\"sub\" \"copy\":"
+ ),
+ Self::Cut => println!(
+ "{:<indent$}Replaces the current subregion by removing from the schematic",
+ "\"sub\" \"cut\":"
+ ),
+ Self::Paste => println!(
+ "{:<indent$}Places a copy of the current subregion into the schematic",
+ "\"sub\" \"paste\":"
+ ),
+ Self::Place => println!(
+ "{:<indent$}Places a block if enough space is available",
+ "\"sub\" \"place\":"
+ ),
+ Self::Rotate => println!(
+ "{:<indent$}Rotates the current subregion (CCW) in increments of 90 degrees",
+ "\"sub\" \"rotate\":"
+ ),
+ Self::Mirror => println!(
+ "{:<indent$}Mirrors the current subregion horizontally or vertically",
+ "\"sub\" \"mirror\":"
+ ),
+ Self::Move => println!(
+ "{:<indent$}Moves all blocks by a certain offset",
+ "\"sub\" \"move\":"
+ ),
+ Self::Resize => println!(
+ "{:<indent$}Resizes the current subregion and offsets it",
+ "\"sub\" \"resize\":"
+ ),
+ Self::Remove => println!(
+ "{:<indent$}Removes blocks at a position or within a region",
+ "\"sub\" \"remove\":"
+ ),
+ Self::Print => println!(
+ "{:<indent$}Prints the current subregion in a visual representation",
+ "\"sub\" \"print\":"
+ ),
+ Self::Dump => println!(
+ "{:<indent$}Prints the current subregion as a base-64 encoded string",
+ "\"sub\" \"dump\":"
+ ),
+ }
+ self.print_usage(indent);
+ }
+
+ fn print_usage(&self, indent: usize) {
+ match self {
+ Self::Help => (),
+ Self::Input => println!(r#"{:indent$} Usage: "sub" "input" <base64>"#, ""),
+ Self::Copy => {
+ println!(
+ r#"{:indent$} Usage: "sub" "copy" <x0> <y0> <x1> <y1> [<strict>]"#,
+ ""
+ );
+ println!(
+ r#"{:indent$} Strictness ignores blocks which are not entirely within the bounds"#,
+ ""
+ );
+ }
+ Self::Cut => {
+ println!(
+ r#"{:indent$} Usage: "sub" "cut" <x0> <y0> <x1> <y1> [<strict>]"#,
+ ""
+ );
+ println!(
+ r#"{:indent$} Strictness ignores blocks which are not entirely within the bounds"#,
+ ""
+ );
+ }
+ Self::Paste => println!(r#"{:indent$} Usage: "sub" "paste" <x> <y>"#, ""),
+ Self::Place => {
+ println!(
+ r#"{:indent$} Usage: "sub" "place" <x> <y> <block name> [<rotation> [<replace>]]"#,
+ ""
+ );
+ println!(
+ r#"{:indent$} Rotation is one of right, up, left, down or compass angles"#,
+ ""
+ )
+ }
+ Self::Rotate => println!(r#"{:indent$} Usage: "sub" "rotate" <angle>"#, ""),
+ Self::Mirror => println!(r#"{:indent$} Usage: "sub" "mirror" <axis>"#, ""),
+ Self::Move => println!(r#"{:indent$} Usage: "sub" "move" <dx> <dy>"#, ""),
+ Self::Resize => println!(
+ r#"{:indent$} Usage: "sub" "resize" <width> <height> [<dx> <dy>]"#,
+ ""
+ ),
+ Self::Remove => println!(
+ r#"{:indent$} Usage: "sub" "remove" <x0> <y0> [<x1> <y1>]"#,
+ ""
+ ),
+ Self::Print | Self::Dump => (),
+ }
+ }
}
-fn interpret_sub(state: &mut State, tokens: &mut Tokenizer)
-{
- match tokens.next()
- {
- None => println!(r#"Empty "sub" command, type "sub help" for more info"#),
- Some("help") =>
- {
- const INDENT: usize = 20;
- println!(r#"List of available commands for \"sub\":"#);
- SubCommand::Help.print_help(INDENT);
- SubCommand::Input.print_help(INDENT);
- SubCommand::Copy.print_help(INDENT);
- SubCommand::Cut.print_help(INDENT);
- SubCommand::Paste.print_help(INDENT);
- SubCommand::Place.print_help(INDENT);
- SubCommand::Rotate.print_help(INDENT);
- SubCommand::Mirror.print_help(INDENT);
- SubCommand::Move.print_help(INDENT);
- SubCommand::Resize.print_help(INDENT);
- SubCommand::Remove.print_help(INDENT);
- SubCommand::Print.print_help(INDENT);
- SubCommand::Dump.print_help(INDENT);
- if tokens.remainder().is_some()
- {
- eprintln!("Extra arguments are considered an error");
- }
- },
- Some("input") =>
- {
- let subregion = match tokens.next()
- {
- None =>
- {
- eprintln!("Missing argument: base64");
- SubCommand::Input.print_usage(0);
- return;
- },
- Some(b64) =>
- {
- match SchematicSerializer(state.reg).deserialize_base64(b64)
- {
- Ok(s) => s,
- Err(e) =>
- {
- print_err!(e, "Could not deserialize schematic");
- return;
- },
- }
- },
- };
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "sub input""#);
- SubCommand::Input.print_usage(0);
- return;
- }
- state.subregion = Some(subregion);
- },
- Some(op @ ("copy" | "cut")) =>
- {
- let modify_original = op == "cut";
- let Some(ref mut schematic) = state.schematic
+fn interpret_sub(state: &mut State, tokens: &mut Tokenizer) {
+ match tokens.next() {
+ None => println!(r#"Empty "sub" command, type "sub help" for more info"#),
+ Some("help") => {
+ const INDENT: usize = 20;
+ println!(r#"List of available commands for \"sub\":"#);
+ SubCommand::Help.print_help(INDENT);
+ SubCommand::Input.print_help(INDENT);
+ SubCommand::Copy.print_help(INDENT);
+ SubCommand::Cut.print_help(INDENT);
+ SubCommand::Paste.print_help(INDENT);
+ SubCommand::Place.print_help(INDENT);
+ SubCommand::Rotate.print_help(INDENT);
+ SubCommand::Mirror.print_help(INDENT);
+ SubCommand::Move.print_help(INDENT);
+ SubCommand::Resize.print_help(INDENT);
+ SubCommand::Remove.print_help(INDENT);
+ SubCommand::Print.print_help(INDENT);
+ SubCommand::Dump.print_help(INDENT);
+ if tokens.remainder().is_some() {
+ eprintln!("Extra arguments are considered an error");
+ }
+ }
+ Some("input") => {
+ let subregion = match tokens.next() {
+ None => {
+ eprintln!("Missing argument: base64");
+ SubCommand::Input.print_usage(0);
+ return;
+ }
+ Some(b64) => match SchematicSerializer(state.reg).deserialize_base64(b64) {
+ Ok(s) => s,
+ Err(e) => {
+ print_err!(e, "Could not deserialize schematic");
+ return;
+ }
+ },
+ };
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "sub input""#);
+ SubCommand::Input.print_usage(0);
+ return;
+ }
+ state.subregion = Some(subregion);
+ }
+ Some(op @ ("copy" | "cut")) => {
+ let modify_original = op == "cut";
+ let Some(ref mut schematic) = state.schematic
else
{
eprintln!(r#"Command "sub {op}" requires an active schematic (see "help")"#);
return;
};
- let x0 = if modify_original {parse_num!(SubCommand::Cut, tokens, "x0", u16)} else {parse_num!(SubCommand::Copy, tokens, "x0", u16)};
- let y0 = if modify_original {parse_num!(SubCommand::Cut, tokens, "y0", u16)} else {parse_num!(SubCommand::Copy, tokens, "y0", u16)};
- if x0 >= schematic.get_width() || y0 >= schematic.get_height()
- {
- eprintln!("Invalid lower coordinate ({x0} / {y0}) out of bounds ({} / {})", schematic.get_width(), schematic.get_height());
- return;
- }
- let x1 = if modify_original {parse_num!(SubCommand::Cut, tokens, "x1", u16)} else {parse_num!(SubCommand::Copy, tokens, "x1", u16)};
- let y1 = if modify_original {parse_num!(SubCommand::Cut, tokens, "y1", u16)} else {parse_num!(SubCommand::Copy, tokens, "y1", u16)};
- if x1 < x0 || y1 < y0
- {
- eprintln!("Invalid upper coordinate ({x1} / {y1}) too low (lower bound {x0} / {y0})");
- return;
- }
- if x1 >= schematic.get_width() || y1 >= schematic.get_height()
- {
- eprintln!("Invalid upper coordinate ({x1} / {y1}) out of bounds ({} / {})", schematic.get_width(), schematic.get_height());
- return;
- }
- let strict = match tokens.next()
- {
- None => false,
- Some("true") | Some("yes") => true,
- Some("false") | Some("no") => false,
- Some(strict) =>
- {
- eprintln!("Invalid strictness {strict:?}");
- return;
- },
- };
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "sub {op}""#);
- SubCommand::Copy.print_usage(0);
- return;
- }
- let mut true_x0 = x0;
- let mut true_y0 = y0;
- let mut true_x1 = x1;
- let mut true_y1 = y1;
- let mut targets = Vec::new();
- for p in schematic.block_iter()
- {
- let sz = p.get_block().get_size() as u16;
- let pos0 = (p.get_pos().0 - (sz - 1) / 2, p.get_pos().1 - (sz - 1) / 2);
- let pos1 = (p.get_pos().0 + sz / 2, p.get_pos().1 + sz / 2);
- let include = if strict {pos0.0 >= x0 && pos0.1 >= y0 && pos1.0 <= x1 && pos1.1 <= y1}
- else {pos0.0 <= x1 && pos0.1 <= y1 && pos1.0 >= x0 && pos1.1 >= y0};
- if include
- {
- targets.push(p.get_pos());
- if !strict
- {
- if pos0.0 < true_x0 {true_x0 = pos0.0;}
- if pos0.1 < true_y0 {true_y0 = pos0.1;}
- if pos1.0 > true_x1 {true_x1 = pos1.0;}
- if pos1.1 > true_y1 {true_y1 = pos1.1;}
- }
- }
- }
- let mut subregion = Schematic::new(true_x1 - true_x0 + 1, true_y1 - true_y0 + 1);
- if !targets.is_empty()
- {
- if modify_original {state.unsaved = true;}
- for GridPos(x, y) in targets
- {
- let mut original: Option<Placement> = None;
- let place = if modify_original
- {
- original = schematic.take(x, y).unwrap();
- original.as_ref().unwrap()
- }
- else {schematic.get(x, y).unwrap().unwrap()};
- let data = match place.get_state()
- {
- None => DynData::Empty,
- Some(d) => place.get_block().serialize_state(d).unwrap(),
- };
- subregion.set(place.get_pos().0 - true_x0, place.get_pos().1 - true_y0, place.get_block(), data, place.get_rotation()).unwrap();
- drop(original);
- }
- }
- state.subregion = Some(subregion);
- },
- Some("paste") =>
- {
- let Some(ref mut schematic) = state.schematic
+ let x0 = if modify_original {
+ parse_num!(SubCommand::Cut, tokens, "x0", u16)
+ } else {
+ parse_num!(SubCommand::Copy, tokens, "x0", u16)
+ };
+ let y0 = if modify_original {
+ parse_num!(SubCommand::Cut, tokens, "y0", u16)
+ } else {
+ parse_num!(SubCommand::Copy, tokens, "y0", u16)
+ };
+ if x0 >= schematic.get_width() || y0 >= schematic.get_height() {
+ eprintln!(
+ "Invalid lower coordinate ({x0} / {y0}) out of bounds ({} / {})",
+ schematic.get_width(),
+ schematic.get_height()
+ );
+ return;
+ }
+ let x1 = if modify_original {
+ parse_num!(SubCommand::Cut, tokens, "x1", u16)
+ } else {
+ parse_num!(SubCommand::Copy, tokens, "x1", u16)
+ };
+ let y1 = if modify_original {
+ parse_num!(SubCommand::Cut, tokens, "y1", u16)
+ } else {
+ parse_num!(SubCommand::Copy, tokens, "y1", u16)
+ };
+ if x1 < x0 || y1 < y0 {
+ eprintln!(
+ "Invalid upper coordinate ({x1} / {y1}) too low (lower bound {x0} / {y0})"
+ );
+ return;
+ }
+ if x1 >= schematic.get_width() || y1 >= schematic.get_height() {
+ eprintln!(
+ "Invalid upper coordinate ({x1} / {y1}) out of bounds ({} / {})",
+ schematic.get_width(),
+ schematic.get_height()
+ );
+ return;
+ }
+ let strict = match tokens.next() {
+ None => false,
+ Some("true") | Some("yes") => true,
+ Some("false") | Some("no") => false,
+ Some(strict) => {
+ eprintln!("Invalid strictness {strict:?}");
+ return;
+ }
+ };
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "sub {op}""#);
+ SubCommand::Copy.print_usage(0);
+ return;
+ }
+ let mut true_x0 = x0;
+ let mut true_y0 = y0;
+ let mut true_x1 = x1;
+ let mut true_y1 = y1;
+ let mut targets = Vec::new();
+ for p in schematic.block_iter() {
+ let sz = p.get_block().get_size() as u16;
+ let pos0 = (p.get_pos().0 - (sz - 1) / 2, p.get_pos().1 - (sz - 1) / 2);
+ let pos1 = (p.get_pos().0 + sz / 2, p.get_pos().1 + sz / 2);
+ let include = if strict {
+ pos0.0 >= x0 && pos0.1 >= y0 && pos1.0 <= x1 && pos1.1 <= y1
+ } else {
+ pos0.0 <= x1 && pos0.1 <= y1 && pos1.0 >= x0 && pos1.1 >= y0
+ };
+ if include {
+ targets.push(p.get_pos());
+ if !strict {
+ if pos0.0 < true_x0 {
+ true_x0 = pos0.0;
+ }
+ if pos0.1 < true_y0 {
+ true_y0 = pos0.1;
+ }
+ if pos1.0 > true_x1 {
+ true_x1 = pos1.0;
+ }
+ if pos1.1 > true_y1 {
+ true_y1 = pos1.1;
+ }
+ }
+ }
+ }
+ let mut subregion = Schematic::new(true_x1 - true_x0 + 1, true_y1 - true_y0 + 1);
+ if !targets.is_empty() {
+ if modify_original {
+ state.unsaved = true;
+ }
+ for GridPos(x, y) in targets {
+ let mut original: Option<Placement> = None;
+ let place = if modify_original {
+ original = schematic.take(x, y).unwrap();
+ original.as_ref().unwrap()
+ } else {
+ schematic.get(x, y).unwrap().unwrap()
+ };
+ let data = match place.get_state() {
+ None => DynData::Empty,
+ Some(d) => place.get_block().serialize_state(d).unwrap(),
+ };
+ subregion
+ .set(
+ place.get_pos().0 - true_x0,
+ place.get_pos().1 - true_y0,
+ place.get_block(),
+ data,
+ place.get_rotation(),
+ )
+ .unwrap();
+ drop(original);
+ }
+ }
+ state.subregion = Some(subregion);
+ }
+ Some("paste") => {
+ let Some(ref mut schematic) = state.schematic
else
{
eprintln!(r#"Command "sub paste" requires an active schematic (see "help")"#);
return;
};
- let Some(ref subregion) = state.subregion
+ let Some(ref subregion) = state.subregion
else
{
eprintln!(r#"Command "sub paste" requires an active subregion (see "sub help")"#);
return;
};
- let x = parse_num!(SubCommand::Paste, tokens, "x", u16);
- let y = parse_num!(SubCommand::Paste, tokens, "y", u16);
- if subregion.get_width() > schematic.get_width() || subregion.get_height() > schematic.get_height()
- {
- eprintln!("Subregion ({} / {}) is larger than schematic ({} / {})", subregion.get_width(), subregion.get_height(),
- schematic.get_width(), schematic.get_height());
- return;
- }
- if x >= schematic.get_width() - subregion.get_width()
- {
- let x1 = x + subregion.get_width() - 1;
- let y1 = y + subregion.get_height() - 1;
- eprintln!("Invalid coordinate ({x} / {y} to {x1} / {y1}) out of bounds ({} / {})", schematic.get_width(), schematic.get_height());
- return;
- }
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "sub paste""#);
- SubCommand::Paste.print_usage(0);
- return;
- }
- state.unsaved = true;
- for p in subregion.block_iter()
- {
- let data = match p.get_state()
- {
- None => DynData::Empty,
- Some(d) => p.get_block().serialize_state(d).unwrap(),
- };
- schematic.replace(x + p.get_pos().0, y + p.get_pos().1, p.get_block(), data, p.get_rotation(), false).unwrap();
- }
- },
- Some("place") =>
- {
- let Some(ref mut subregion) = state.subregion
+ let x = parse_num!(SubCommand::Paste, tokens, "x", u16);
+ let y = parse_num!(SubCommand::Paste, tokens, "y", u16);
+ if subregion.get_width() > schematic.get_width()
+ || subregion.get_height() > schematic.get_height()
+ {
+ eprintln!(
+ "Subregion ({} / {}) is larger than schematic ({} / {})",
+ subregion.get_width(),
+ subregion.get_height(),
+ schematic.get_width(),
+ schematic.get_height()
+ );
+ return;
+ }
+ if x >= schematic.get_width() - subregion.get_width() {
+ let x1 = x + subregion.get_width() - 1;
+ let y1 = y + subregion.get_height() - 1;
+ eprintln!(
+ "Invalid coordinate ({x} / {y} to {x1} / {y1}) out of bounds ({} / {})",
+ schematic.get_width(),
+ schematic.get_height()
+ );
+ return;
+ }
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "sub paste""#);
+ SubCommand::Paste.print_usage(0);
+ return;
+ }
+ state.unsaved = true;
+ for p in subregion.block_iter() {
+ let data = match p.get_state() {
+ None => DynData::Empty,
+ Some(d) => p.get_block().serialize_state(d).unwrap(),
+ };
+ schematic
+ .replace(
+ x + p.get_pos().0,
+ y + p.get_pos().1,
+ p.get_block(),
+ data,
+ p.get_rotation(),
+ false,
+ )
+ .unwrap();
+ }
+ }
+ Some("place") => {
+ let Some(ref mut subregion) = state.subregion
else
{
eprintln!(r#"Command "sub place" requires an active subregion (see "sub help")"#);
return;
};
- let x = parse_num!(SubCommand::Place, tokens, "x", u16);
- let y = parse_num!(SubCommand::Place, tokens, "y", u16);
- if x >= subregion.get_width() || y >= subregion.get_height()
- {
- eprintln!("Invalid coordinate ({x} / {y}) out of bounds ({} / {})", subregion.get_width(), subregion.get_height());
- return;
- }
- let block = match tokens.next()
- {
- None =>
- {
- eprintln!("Missing argument: block name");
- SubCommand::Place.print_usage(0);
- return;
- },
- Some(name) =>
- {
- match state.reg.get(name)
- {
- None =>
- {
- eprintln!("No such block {name:?}");
- return;
- },
- Some(b) => b,
- }
- },
- };
- let rot = match tokens.next()
- {
- None => None,
- Some("right") | Some("east") => Some(Rotation::Right),
- Some("up") | Some("north") => Some(Rotation::Up),
- Some("left") | Some("west") => Some(Rotation::Left),
- Some("down") | Some("south") => Some(Rotation::Down),
- Some(rot) =>
- {
- eprintln!("Invalid rotation {rot:?}");
- return;
- },
- };
- let replace = if rot.is_some()
- {
- match tokens.next()
- {
- None => None,
- Some("true") | Some("yes") => Some(true),
- Some("false") | Some("no") => Some(false),
- Some(replace) =>
- {
- eprintln!("Invalid replacement {replace:?}");
- return;
- },
- }
- }
- else {None};
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "sub place""#);
- SubCommand::Place.print_usage(0);
- return;
- }
- let rot = rot.unwrap_or(Rotation::Right);
- let result = if replace.unwrap_or(false)
- {
- subregion.replace(x, y, block, DynData::Empty, rot, false).err()
- }
- else
- {
- subregion.set(x, y, block, DynData::Empty, rot).err()
- };
- if let Some(e) = result
- {
- print_err!(e, "Failed to place block at {x} / {y}");
- return;
- }
- },
- Some("rotate") =>
- {
- let Some(ref mut subregion) = state.subregion
+ let x = parse_num!(SubCommand::Place, tokens, "x", u16);
+ let y = parse_num!(SubCommand::Place, tokens, "y", u16);
+ if x >= subregion.get_width() || y >= subregion.get_height() {
+ eprintln!(
+ "Invalid coordinate ({x} / {y}) out of bounds ({} / {})",
+ subregion.get_width(),
+ subregion.get_height()
+ );
+ return;
+ }
+ let block = match tokens.next() {
+ None => {
+ eprintln!("Missing argument: block name");
+ SubCommand::Place.print_usage(0);
+ return;
+ }
+ Some(name) => match state.reg.get(name) {
+ None => {
+ eprintln!("No such block {name:?}");
+ return;
+ }
+ Some(b) => b,
+ },
+ };
+ let rot = match tokens.next() {
+ None => None,
+ Some("right") | Some("east") => Some(Rotation::Right),
+ Some("up") | Some("north") => Some(Rotation::Up),
+ Some("left") | Some("west") => Some(Rotation::Left),
+ Some("down") | Some("south") => Some(Rotation::Down),
+ Some(rot) => {
+ eprintln!("Invalid rotation {rot:?}");
+ return;
+ }
+ };
+ let replace = if rot.is_some() {
+ match tokens.next() {
+ None => None,
+ Some("true") | Some("yes") => Some(true),
+ Some("false") | Some("no") => Some(false),
+ Some(replace) => {
+ eprintln!("Invalid replacement {replace:?}");
+ return;
+ }
+ }
+ } else {
+ None
+ };
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "sub place""#);
+ SubCommand::Place.print_usage(0);
+ return;
+ }
+ let rot = rot.unwrap_or(Rotation::Right);
+ let result = if replace.unwrap_or(false) {
+ subregion
+ .replace(x, y, block, DynData::Empty, rot, false)
+ .err()
+ } else {
+ subregion.set(x, y, block, DynData::Empty, rot).err()
+ };
+ if let Some(e) = result {
+ print_err!(e, "Failed to place block at {x} / {y}");
+ return;
+ }
+ }
+ Some("rotate") => {
+ let Some(ref mut subregion) = state.subregion
else
{
eprintln!(r#"Command "sub rotate" requires an active subgregion (see "sub help")"#);
return;
};
- let angle = parse_num!(SubCommand::Rotate, tokens, "angle", i32);
- if angle % 90 != 0
- {
- eprintln!("Rotation angle must be a multiple of 90 degrees");
- return;
- }
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "sub rotate""#);
- SubCommand::Rotate.print_usage(0);
- return;
- }
- match (angle / 90) % 4
- {
- 0 => (),
- 1 | -3 => subregion.rotate(false),
- 2 | -2 => subregion.rotate_180(),
- 3 | -1 => subregion.rotate(true),
- a => unreachable!("angle {angle} -> {a}"),
- }
- },
- Some("mirror") =>
- {
- let Some(ref mut subregion) = state.subregion
+ let angle = parse_num!(SubCommand::Rotate, tokens, "angle", i32);
+ if angle % 90 != 0 {
+ eprintln!("Rotation angle must be a multiple of 90 degrees");
+ return;
+ }
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "sub rotate""#);
+ SubCommand::Rotate.print_usage(0);
+ return;
+ }
+ match (angle / 90) % 4 {
+ 0 => (),
+ 1 | -3 => subregion.rotate(false),
+ 2 | -2 => subregion.rotate_180(),
+ 3 | -1 => subregion.rotate(true),
+ a => unreachable!("angle {angle} -> {a}"),
+ }
+ }
+ Some("mirror") => {
+ let Some(ref mut subregion) = state.subregion
else
{
eprintln!(r#"Command "sub mirror" requires an active subregion (see "sub help")"#);
return;
};
- let (x, y) = match tokens.next()
- {
- None =>
- {
- eprintln!("Missing argument: axis");
- SubCommand::Mirror.print_usage(0);
- return;
- },
- Some("x") | Some("h") | Some("horizontal") | Some("horizontally") => (true, false),
- Some("y") | Some("v") | Some("vertical") | Some("vertically") => (false, true),
- Some("both") | Some("all") => (true, true),
- Some(axis) =>
- {
- eprintln!("Invalid mirroring axis: {axis:?}");
- return;
- },
- };
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "sub mirror""#);
- SubCommand::Mirror.print_usage(0);
- return;
- }
- subregion.mirror(x, y);
- },
- Some("move") =>
- {
- let Some(ref mut subregion) = state.subregion
+ let (x, y) = match tokens.next() {
+ None => {
+ eprintln!("Missing argument: axis");
+ SubCommand::Mirror.print_usage(0);
+ return;
+ }
+ Some("x") | Some("h") | Some("horizontal") | Some("horizontally") => (true, false),
+ Some("y") | Some("v") | Some("vertical") | Some("vertically") => (false, true),
+ Some("both") | Some("all") => (true, true),
+ Some(axis) => {
+ eprintln!("Invalid mirroring axis: {axis:?}");
+ return;
+ }
+ };
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "sub mirror""#);
+ SubCommand::Mirror.print_usage(0);
+ return;
+ }
+ subregion.mirror(x, y);
+ }
+ Some("move") => {
+ let Some(ref mut subregion) = state.subregion
else
{
eprintln!(r#"Command "sub move" requires an active subregion (see "sub help")"#);
return;
};
- let dx = parse_num!(SubCommand::Move, tokens, "dx", i16);
- let dy = parse_num!(SubCommand::Move, tokens, "dy", i16);
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "sub move""#);
- SubCommand::Move.print_usage(0);
- return;
- }
- if dx != 0 && dy != 0
- {
- if let Err(e) = subregion.resize(dx, dy, subregion.get_width(), subregion.get_height())
- {
- match e
- {
- ResizeError::XOffset{dx, old_w, new_w} =>
- {
- debug_assert_eq!(old_w, new_w);
- eprintln!("Invalid horizontal move {dx} not in ]-{old_w}, {new_w}[");
- },
- ResizeError::YOffset{dy, old_h, new_h} =>
- {
- debug_assert_eq!(old_h, new_h);
- eprintln!("Invalid vertical move {dy} not in ]-{old_h}, {new_h}[");
- },
- ResizeError::Truncated{right, top, left, bottom} =>
- {
- eprint!("Move would truncate subregion: ");
- let mut first = true;
- if right > 0
- {
- eprint!("{}right: {right}", if first {""} else {", "});
- first = false;
- }
- if top > 0
- {
- eprint!("{}top: {top}", if first {""} else {", "});
- first = false;
- }
- if left > 0
- {
- eprint!("{}left: {left}", if first {""} else {", "});
- first = false;
- }
- if bottom > 0
- {
- eprint!("{}bottom: {bottom}", if first {""} else {", "});
- first = false;
- }
- if first {eprintln!("<unknown>");} else {eprintln!();}
- },
- _ => print_err!(e, "Unexpected resize error (for {dx} / {dy})")
- }
- }
- }
- },
- Some("resize") =>
- {
- let Some(ref mut subregion) = state.subregion
+ let dx = parse_num!(SubCommand::Move, tokens, "dx", i16);
+ let dy = parse_num!(SubCommand::Move, tokens, "dy", i16);
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "sub move""#);
+ SubCommand::Move.print_usage(0);
+ return;
+ }
+ if dx != 0 && dy != 0 {
+ if let Err(e) =
+ subregion.resize(dx, dy, subregion.get_width(), subregion.get_height())
+ {
+ match e {
+ ResizeError::XOffset { dx, old_w, new_w } => {
+ debug_assert_eq!(old_w, new_w);
+ eprintln!("Invalid horizontal move {dx} not in ]-{old_w}, {new_w}[");
+ }
+ ResizeError::YOffset { dy, old_h, new_h } => {
+ debug_assert_eq!(old_h, new_h);
+ eprintln!("Invalid vertical move {dy} not in ]-{old_h}, {new_h}[");
+ }
+ ResizeError::Truncated {
+ right,
+ top,
+ left,
+ bottom,
+ } => {
+ eprint!("Move would truncate subregion: ");
+ let mut first = true;
+ if right > 0 {
+ eprint!("{}right: {right}", if first { "" } else { ", " });
+ first = false;
+ }
+ if top > 0 {
+ eprint!("{}top: {top}", if first { "" } else { ", " });
+ first = false;
+ }
+ if left > 0 {
+ eprint!("{}left: {left}", if first { "" } else { ", " });
+ first = false;
+ }
+ if bottom > 0 {
+ eprint!("{}bottom: {bottom}", if first { "" } else { ", " });
+ first = false;
+ }
+ if first {
+ eprintln!("<unknown>");
+ } else {
+ eprintln!();
+ }
+ }
+ _ => print_err!(e, "Unexpected resize error (for {dx} / {dy})"),
+ }
+ }
+ }
+ }
+ Some("resize") => {
+ let Some(ref mut subregion) = state.subregion
else
{
eprintln!(r#"Command "sub resize" requires an active subregion (see "sub help")"#);
return;
};
- let w = parse_num!(SubCommand::Resize, tokens, "width", u16);
- if w == 0
- {
- eprintln!("Subregion width must be positive");
- return;
- }
- let h = parse_num!(SubCommand::Resize, tokens, "height", u16);
- if h == 0
- {
- eprintln!("Subregion height must be positive");
- return;
- }
- let (dx, dy) = if let arg @ Some(..) = tokens.next()
- {
- let dx = parse_num!(SubCommand::Resize, "dx", <i16>::from(arg));
- let dy = parse_num!(SubCommand::Resize, tokens, "dy", i16);
- (dx, dy)
- }
- else {(0, 0)};
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "sub resize""#);
- SubCommand::Resize.print_usage(0);
- return;
- }
- if w != subregion.get_width() || h != subregion.get_height() || dx != 0 || dy != 0
- {
- if let Err(e) = subregion.resize(dx, dy, w, h)
- {
- print_err!(e, "Could not resize subregion");
- }
- }
- },
- Some("remove") =>
- {
- let Some(ref mut subregion) = state.subregion
+ let w = parse_num!(SubCommand::Resize, tokens, "width", u16);
+ if w == 0 {
+ eprintln!("Subregion width must be positive");
+ return;
+ }
+ let h = parse_num!(SubCommand::Resize, tokens, "height", u16);
+ if h == 0 {
+ eprintln!("Subregion height must be positive");
+ return;
+ }
+ let (dx, dy) = if let arg @ Some(..) = tokens.next() {
+ let dx = parse_num!(SubCommand::Resize, "dx", <i16>::from(arg));
+ let dy = parse_num!(SubCommand::Resize, tokens, "dy", i16);
+ (dx, dy)
+ } else {
+ (0, 0)
+ };
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "sub resize""#);
+ SubCommand::Resize.print_usage(0);
+ return;
+ }
+ if w != subregion.get_width() || h != subregion.get_height() || dx != 0 || dy != 0 {
+ if let Err(e) = subregion.resize(dx, dy, w, h) {
+ print_err!(e, "Could not resize subregion");
+ }
+ }
+ }
+ Some("remove") => {
+ let Some(ref mut subregion) = state.subregion
else
{
eprintln!(r#"Command "sub remove" requires an active subregion (see "sub help")"#);
return;
};
- let x0 = parse_num!(SubCommand::Remove, tokens, "x0", u16);
- let y0 = parse_num!(SubCommand::Remove, tokens, "y0", u16);
- if x0 >= subregion.get_width() || y0 >= subregion.get_height()
- {
- eprintln!("Invalid coordinate ({x0} / {y0}) out of bounds ({} / {})", subregion.get_width(), subregion.get_height());
- return;
- }
- let (x0, y0, x1, y1) = if let arg @ Some(..) = tokens.next()
- {
- let x1 = parse_num!(SubCommand::Remove, "x1", <u16>::from(arg));
- let y1 = parse_num!(SubCommand::Remove, tokens, "y1", u16);
- if x1 >= subregion.get_width() || y1 >= subregion.get_height()
- {
- eprintln!("Invalid coordinate ({x1} / {y1}) out of bounds ({} / {})", subregion.get_width(), subregion.get_height());
- return;
- }
- (x0.min(x1), y0.min(y1), x0.max(x1), y0.max(y1))
- }
- else {(x0, y0, x0, y0)};
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "sub remove""#);
- SubCommand::Remove.print_usage(0);
- return;
- }
- if x1 > x0 || y1 > y0
- {
- let mut cnt = 0u32;
- for y in y0..=y1
- {
- for x in x0..=x1
- {
- // position was already checked while parsing
- if subregion.take(x, y).unwrap().is_some() {cnt += 1;}
- }
- }
- println!("Removed {cnt} blocks in {x0} / {y0} to {x1} / {y1}");
- }
- else
- {
- // position was already checked while parsing
- match subregion.take(x0, y0).unwrap()
- {
- None => (),
- Some(p) => println!("Removed block {} from {x0} / {y0}", p.get_block().get_name()),
- }
- }
- },
- Some("print") =>
- {
- let Some(ref subregion) = state.subregion
+ let x0 = parse_num!(SubCommand::Remove, tokens, "x0", u16);
+ let y0 = parse_num!(SubCommand::Remove, tokens, "y0", u16);
+ if x0 >= subregion.get_width() || y0 >= subregion.get_height() {
+ eprintln!(
+ "Invalid coordinate ({x0} / {y0}) out of bounds ({} / {})",
+ subregion.get_width(),
+ subregion.get_height()
+ );
+ return;
+ }
+ let (x0, y0, x1, y1) = if let arg @ Some(..) = tokens.next() {
+ let x1 = parse_num!(SubCommand::Remove, "x1", <u16>::from(arg));
+ let y1 = parse_num!(SubCommand::Remove, tokens, "y1", u16);
+ if x1 >= subregion.get_width() || y1 >= subregion.get_height() {
+ eprintln!(
+ "Invalid coordinate ({x1} / {y1}) out of bounds ({} / {})",
+ subregion.get_width(),
+ subregion.get_height()
+ );
+ return;
+ }
+ (x0.min(x1), y0.min(y1), x0.max(x1), y0.max(y1))
+ } else {
+ (x0, y0, x0, y0)
+ };
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "sub remove""#);
+ SubCommand::Remove.print_usage(0);
+ return;
+ }
+ if x1 > x0 || y1 > y0 {
+ let mut cnt = 0u32;
+ for y in y0..=y1 {
+ for x in x0..=x1 {
+ // position was already checked while parsing
+ if subregion.take(x, y).unwrap().is_some() {
+ cnt += 1;
+ }
+ }
+ }
+ println!("Removed {cnt} blocks in {x0} / {y0} to {x1} / {y1}");
+ } else {
+ // position was already checked while parsing
+ match subregion.take(x0, y0).unwrap() {
+ None => (),
+ Some(p) => println!(
+ "Removed block {} from {x0} / {y0}",
+ p.get_block().get_name()
+ ),
+ }
+ }
+ }
+ Some("print") => {
+ let Some(ref subregion) = state.subregion
else
{
eprintln!(r#"Command "sub print" requires an active subregion (see "sub help")"#);
return;
};
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "sub print""#);
- SubCommand::Print.print_usage(0);
- return;
- }
- print_schematic(subregion);
- },
- Some("dump") =>
- {
- let Some(ref subregion) = state.subregion
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "sub print""#);
+ SubCommand::Print.print_usage(0);
+ return;
+ }
+ print_schematic(subregion);
+ }
+ Some("dump") => {
+ let Some(ref subregion) = state.subregion
else
{
eprintln!(r#"Command "sub dump" requires an active subregion (see "sub help")"#);
return;
};
- if tokens.remainder().is_some()
- {
- eprintln!(r#"Too many parameters for "sub dump""#);
- SubCommand::Dump.print_usage(0);
- return;
- }
- let b64 = match SchematicSerializer(state.reg).serialize_base64(subregion)
- {
- Ok(b64) => b64,
- Err(e) =>
- {
- print_err!(e, "Could not serialize subregion");
- return;
- },
- };
- println!("Subregion: {}", b64);
- },
- Some(unknown) => eprintln!("Unknown command \"sub\" {unknown:?}"),
- }
+ if tokens.remainder().is_some() {
+ eprintln!(r#"Too many parameters for "sub dump""#);
+ SubCommand::Dump.print_usage(0);
+ return;
+ }
+ let b64 = match SchematicSerializer(state.reg).serialize_base64(subregion) {
+ Ok(b64) => b64,
+ Err(e) => {
+ print_err!(e, "Could not serialize subregion");
+ return;
+ }
+ };
+ println!("Subregion: {}", b64);
+ }
+ Some(unknown) => eprintln!("Unknown command \"sub\" {unknown:?}"),
+ }
}
diff --git a/src/exe/mod.rs b/src/exe/mod.rs
index 2c6113b..801e122 100644
--- a/src/exe/mod.rs
+++ b/src/exe/mod.rs
@@ -8,7 +8,7 @@ macro_rules!print_err
{
{
use std::error::Error;
-
+
let err = $err;
eprint!($($msg)*);
eprintln!(": {err}");
@@ -27,15 +27,13 @@ macro_rules!print_err
}
pub(crate) use print_err;
-fn main()
-{
- let mut args = std::env::args();
- args.next().unwrap(); // path to executable
- match args.next()
- {
- None => eprintln!("Not enough arguments, valid commands are: edit, print"),
- Some(s) if s == "edit" => edit::main(args, 1),
- Some(s) if s == "print" => print::main(args, 1),
- Some(s) => eprintln!("Unknown argument {s}, valid commands are: edit, print"),
- }
-} \ No newline at end of file
+fn main() {
+ let mut args = std::env::args();
+ args.next().unwrap(); // path to executable
+ match args.next() {
+ None => eprintln!("Not enough arguments, valid commands are: edit, print"),
+ Some(s) if s == "edit" => edit::main(args, 1),
+ Some(s) if s == "print" => print::main(args, 1),
+ Some(s) => eprintln!("Unknown argument {s}, valid commands are: edit, print"),
+ }
+}
diff --git a/src/exe/print.rs b/src/exe/print.rs
index 89f6667..9a7faae 100644
--- a/src/exe/print.rs
+++ b/src/exe/print.rs
@@ -1,173 +1,184 @@
use std::borrow::Cow;
use std::env::Args;
-use std::io::{self, Write};
use std::fs;
+use std::io::{self, Write};
use plandustry::block::build_registry;
-use plandustry::data::{DataRead, Serializer};
use plandustry::data::schematic::{Schematic, SchematicSerializer};
+use plandustry::data::{DataRead, Serializer};
-use crate::print_err;
use crate::args::{self, ArgCount, ArgOption, OptionHandler};
+use crate::print_err;
+
+pub fn main(mut args: Args, arg_off: usize) {
+ let mut handler = OptionHandler::new();
+ let opt_file = handler
+ .add(ArgOption::new(
+ Some('f'),
+ Some(Cow::Borrowed("file")),
+ ArgCount::Required(usize::MAX),
+ ))
+ .unwrap();
+ let opt_interact = handler
+ .add(ArgOption::new(
+ Some('i'),
+ Some(Cow::Borrowed("interactive")),
+ ArgCount::Forbidden,
+ ))
+ .unwrap();
+ if let Err(e) = args::parse(&mut args, &mut handler, arg_off) {
+ print_err!(e, "Command error");
+ return;
+ }
-pub fn main(mut args: Args, arg_off: usize)
-{
- let mut handler = OptionHandler::new();
- let opt_file = handler.add(ArgOption::new(Some('f'), Some(Cow::Borrowed("file")), ArgCount::Required(usize::MAX))).unwrap();
- let opt_interact = handler.add(ArgOption::new(Some('i'), Some(Cow::Borrowed("interactive")), ArgCount::Forbidden)).unwrap();
- if let Err(e) = args::parse(&mut args, &mut handler, arg_off)
- {
- print_err!(e, "Command error");
- return;
- }
-
- let reg = build_registry();
- let mut ss = SchematicSerializer(&reg);
- let mut first = true;
- let mut need_space = false;
- // process the files if any
- let file = match handler.get_value(opt_file).get_values()
- {
- None => false,
- Some(paths) =>
- {
- for path in paths
- {
- match fs::read(path)
- {
- Ok(data) =>
- {
- match ss.deserialize(&mut DataRead::new(&data))
- {
- Ok(s) =>
- {
- if !first || need_space {println!();}
- first = false;
- need_space = true;
- println!("Schematic: @{path}");
- print_schematic(&s);
- },
- // continue processing files, literals & maybe interactive mode
- Err(e) =>
- {
- if need_space {println!();}
- first = false;
- need_space = false;
- print_err!(e, "Could not read schematic from {path}");
- },
- }
- },
- // continue processing files, literals & maybe interactive mode
- Err(e) =>
- {
- if need_space {println!();}
- first = false;
- need_space = false;
- print_err!(e, "Could not read file {path:?}");
- },
- }
- }
- true
- },
- };
- // process schematics from command line
- for curr in handler.get_literals()
- {
- match ss.deserialize_base64(curr)
- {
- Ok(s) =>
- {
- if !first || need_space {println!();}
- first = false;
- need_space = true;
- println!("Schematic: {curr}");
- print_schematic(&s);
- },
- // continue processing literals & maybe interactive mode
- Err(e) =>
- {
- if need_space {println!();}
- first = false;
- need_space = false;
- print_err!(e, "Could not read schematic");
- },
- }
- }
- // if --interactive or no schematics: continue parsing from console
- if handler.get_value(opt_interact).is_present() || (!file && handler.get_literals().is_empty())
- {
- if need_space {println!();}
- need_space = false;
- println!("Entering interactive mode, paste schematic to print details.");
- let mut buff = String::new();
- let stdin = io::stdin();
- loop
- {
- buff.clear();
- if need_space {println!();}
- need_space = false;
- print!("> ");
- if let Err(e) = io::stdout().flush()
- {
- // what the print & println macros would do
- panic!("failed printing to stdout: {e}");
- }
- match stdin.read_line(&mut buff)
- {
- Ok(..) =>
- {
- let data = buff.trim();
- if data.is_empty() {break;}
- match ss.deserialize_base64(data)
- {
- Ok(s) =>
- {
- println!();
- need_space = true;
- print_schematic(&s)
- },
- // continue interactive mode, typos are especially likely here
- Err(e) =>
- {
- if need_space {println!();}
- need_space = false;
- print_err!(e, "Could not read schematic");
- },
- }
- },
- Err(e) =>
- {
- if need_space {println!();}
- print_err!(e, "Failed to read next schematic");
- break;
- },
- }
- }
- }
+ let reg = build_registry();
+ let mut ss = SchematicSerializer(&reg);
+ let mut first = true;
+ let mut need_space = false;
+ // process the files if any
+ let file = match handler.get_value(opt_file).get_values() {
+ None => false,
+ Some(paths) => {
+ for path in paths {
+ match fs::read(path) {
+ Ok(data) => {
+ match ss.deserialize(&mut DataRead::new(&data)) {
+ Ok(s) => {
+ if !first || need_space {
+ println!();
+ }
+ first = false;
+ need_space = true;
+ println!("Schematic: @{path}");
+ print_schematic(&s);
+ }
+ // continue processing files, literals & maybe interactive mode
+ Err(e) => {
+ if need_space {
+ println!();
+ }
+ first = false;
+ need_space = false;
+ print_err!(e, "Could not read schematic from {path}");
+ }
+ }
+ }
+ // continue processing files, literals & maybe interactive mode
+ Err(e) => {
+ if need_space {
+ println!();
+ }
+ first = false;
+ need_space = false;
+ print_err!(e, "Could not read file {path:?}");
+ }
+ }
+ }
+ true
+ }
+ };
+ // process schematics from command line
+ for curr in handler.get_literals() {
+ match ss.deserialize_base64(curr) {
+ Ok(s) => {
+ if !first || need_space {
+ println!();
+ }
+ first = false;
+ need_space = true;
+ println!("Schematic: {curr}");
+ print_schematic(&s);
+ }
+ // continue processing literals & maybe interactive mode
+ Err(e) => {
+ if need_space {
+ println!();
+ }
+ first = false;
+ need_space = false;
+ print_err!(e, "Could not read schematic");
+ }
+ }
+ }
+ // if --interactive or no schematics: continue parsing from console
+ if handler.get_value(opt_interact).is_present() || (!file && handler.get_literals().is_empty())
+ {
+ if need_space {
+ println!();
+ }
+ need_space = false;
+ println!("Entering interactive mode, paste schematic to print details.");
+ let mut buff = String::new();
+ let stdin = io::stdin();
+ loop {
+ buff.clear();
+ if need_space {
+ println!();
+ }
+ need_space = false;
+ print!("> ");
+ if let Err(e) = io::stdout().flush() {
+ // what the print & println macros would do
+ panic!("failed printing to stdout: {e}");
+ }
+ match stdin.read_line(&mut buff) {
+ Ok(..) => {
+ let data = buff.trim();
+ if data.is_empty() {
+ break;
+ }
+ match ss.deserialize_base64(data) {
+ Ok(s) => {
+ println!();
+ need_space = true;
+ print_schematic(&s)
+ }
+ // continue interactive mode, typos are especially likely here
+ Err(e) => {
+ if need_space {
+ println!();
+ }
+ need_space = false;
+ print_err!(e, "Could not read schematic");
+ }
+ }
+ }
+ Err(e) => {
+ if need_space {
+ println!();
+ }
+ print_err!(e, "Failed to read next schematic");
+ break;
+ }
+ }
+ }
+ }
}
-pub fn print_schematic(s: &Schematic)
-{
- if let Some(name) = s.get_tags().get("name")
- {
- if !name.is_empty() {println!("Name: {name}");}
- }
- if let Some(desc) = s.get_tags().get("description")
- {
- if !desc.is_empty() {println!("Desc: {desc}");}
- }
- if let Some(labels) = s.get_tags().get("labels")
- {
- if !labels.is_empty() && labels != "[]" {println!("Tags: {:?}", labels);}
- }
- let (cost, sandbox) = s.compute_total_cost();
- if !cost.is_empty()
- {
- println!("Build cost: {cost}{}", if sandbox {" (Sandbox only)"} else {""});
- }
- else if sandbox
- {
- println!("Can only be built in the Sandbox");
- }
- println!("\n{s}");
+pub fn print_schematic(s: &Schematic) {
+ if let Some(name) = s.get_tags().get("name") {
+ if !name.is_empty() {
+ println!("Name: {name}");
+ }
+ }
+ if let Some(desc) = s.get_tags().get("description") {
+ if !desc.is_empty() {
+ println!("Desc: {desc}");
+ }
+ }
+ if let Some(labels) = s.get_tags().get("labels") {
+ if !labels.is_empty() && labels != "[]" {
+ println!("Tags: {:?}", labels);
+ }
+ }
+ let (cost, sandbox) = s.compute_total_cost();
+ if !cost.is_empty() {
+ println!(
+ "Build cost: {cost}{}",
+ if sandbox { " (Sandbox only)" } else { "" }
+ );
+ } else if sandbox {
+ println!("Can only be built in the Sandbox");
+ }
+ println!("\n{s}");
}
diff --git a/src/fluid/mod.rs b/src/fluid/mod.rs
index f0c643e..f4d9395 100644
--- a/src/fluid/mod.rs
+++ b/src/fluid/mod.rs
@@ -1,19 +1,18 @@
use crate::content::content_enum;
-content_enum!
-{
- pub enum Type / Fluid for u16 | TryFromU16Error
- {
- Water => "water",
- Slag => "slag",
- Oil => "oil",
- Cryofluid => "cryofluid",
- Neoplasm => "neoplasm",
- Arkycite => "arkycite",
- Gallium => "gallium",
- Ozone => "ozone",
- Hydrogen => "hydrogen",
- Nitrogen => "nitrogen",
- Cyanogen => "cyanogen",
- }
+content_enum! {
+ pub enum Type / Fluid for u16 | TryFromU16Error
+ {
+ "water",
+ "slag",
+ "oil",
+ "cryofluid",
+ "neoplasm",
+ "arkycite",
+ "gallium",
+ "ozone",
+ "hydrogen",
+ "nitrogen",
+ "cyanogen",
+ }
}
diff --git a/src/item/mod.rs b/src/item/mod.rs
index 8e25b62..4aae21e 100644
--- a/src/item/mod.rs
+++ b/src/item/mod.rs
@@ -4,63 +4,59 @@ use crate::content::content_enum;
pub mod storage;
-content_enum!
-{
- pub enum Type / Item for u16 | TryFromU16Error
- {
- Copper => "copper",
- Lead => "lead",
- Metaglass => "metaglass",
- Graphite => "graphite",
- Sand => "sand",
- Coal => "coal",
- Titanium => "titanium",
- Thorium => "thorium",
- Scrap => "scrap",
- Silicon => "silicon",
- Plastanium => "plastanium",
- PhaseFabric => "phase-fabric",
- SurgeAlloy => "surge-alloy",
- SporePod => "spore-pod",
- BlastCompound => "blast-compound",
- Pyratite => "pyratite",
- Beryllium => "beryllium",
- Tungsten => "tungsten",
- Oxide => "oxide",
- Carbide => "carbide",
- FissileMatter => "fissile-matter",
- DormantCyst => "dormant-cyst",
- }
+content_enum! {
+ pub enum Type / Item for u16 | TryFromU16Error
+ {
+ "copper",
+ "lead",
+ "metaglass",
+ "graphite",
+ "sand",
+ "coal",
+ "titanium",
+ "thorium",
+ "scrap",
+ "silicon",
+ "plastanium",
+ "phase-fabric",
+ "surge-alloy",
+ "spore-pod",
+ "blast-compound",
+ "pyratite",
+ "beryllium",
+ "tungsten",
+ "oxide",
+ "carbide",
+ "fissile-matter",
+ "dormant-cyst",
+ }
}
-impl fmt::Display for Type
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Copper => f.write_str("Copper"),
- Self::Lead => f.write_str("Lead"),
- Self::Metaglass => f.write_str("Metaglass"),
- Self::Graphite => f.write_str("Graphite"),
- Self::Sand => f.write_str("Sand"),
- Self::Coal => f.write_str("Coal"),
- Self::Titanium => f.write_str("Titanium"),
- Self::Thorium => f.write_str("Thorium"),
- Self::Scrap => f.write_str("Scrap"),
- Self::Silicon => f.write_str("Silicon"),
- Self::Plastanium => f.write_str("Plastanium"),
- Self::PhaseFabric => f.write_str("Phase Fabric"),
- Self::SurgeAlloy => f.write_str("Surge Alloy"),
- Self::SporePod => f.write_str("Spore Pod"),
- Self::BlastCompound => f.write_str("Blast Compound"),
- Self::Pyratite => f.write_str("Pyratite"),
- Self::Beryllium => f.write_str("Beryllium"),
- Self::Tungsten => f.write_str("Tungsten"),
- Self::Oxide => f.write_str("Oxide"),
- Self::Carbide => f.write_str("Carbide"),
- Self::FissileMatter => f.write_str("Fissile Matter"),
- Self::DormantCyst => f.write_str("Dormant Cyst"),
- }
- }
+impl fmt::Display for Type {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Copper => f.write_str("Copper"),
+ Self::Lead => f.write_str("Lead"),
+ Self::Metaglass => f.write_str("Metaglass"),
+ Self::Graphite => f.write_str("Graphite"),
+ Self::Sand => f.write_str("Sand"),
+ Self::Coal => f.write_str("Coal"),
+ Self::Titanium => f.write_str("Titanium"),
+ Self::Thorium => f.write_str("Thorium"),
+ Self::Scrap => f.write_str("Scrap"),
+ Self::Silicon => f.write_str("Silicon"),
+ Self::Plastanium => f.write_str("Plastanium"),
+ Self::PhaseFabric => f.write_str("Phase Fabric"),
+ Self::SurgeAlloy => f.write_str("Surge Alloy"),
+ Self::SporePod => f.write_str("Spore Pod"),
+ Self::BlastCompound => f.write_str("Blast Compound"),
+ Self::Pyratite => f.write_str("Pyratite"),
+ Self::Beryllium => f.write_str("Beryllium"),
+ Self::Tungsten => f.write_str("Tungsten"),
+ Self::Oxide => f.write_str("Oxide"),
+ Self::Carbide => f.write_str("Carbide"),
+ Self::FissileMatter => f.write_str("Fissile Matter"),
+ Self::DormantCyst => f.write_str("Dormant Cyst"),
+ }
+ }
}
diff --git a/src/item/storage.rs b/src/item/storage.rs
index 5f57b75..fd5be32 100644
--- a/src/item/storage.rs
+++ b/src/item/storage.rs
@@ -6,405 +6,388 @@ use std::slice;
use crate::item;
#[derive(Clone, Debug, Eq)]
-pub struct Storage
-{
- base: Vec<u32>,
- total: u64,
+pub struct Storage {
+ base: Vec<u32>,
+ total: u64,
}
-impl Storage
-{
- pub const fn new() -> Self
- {
- Self{base: Vec::new(), total: 0}
- }
-
- pub fn is_empty(&self) -> bool
- {
- self.total == 0
- }
-
- pub fn get_total(&self) -> u64
- {
- self.total
- }
-
- pub fn get(&self, ty: item::Type) -> u32
- {
- match self.base.get(u16::from(ty) as usize)
- {
- None => 0,
- Some(cnt) => *cnt,
- }
- }
-
- pub fn set(&mut self, ty: item::Type, count: u32) -> u32
- {
- let idx = u16::from(ty) as usize;
- match self.base.get_mut(idx)
- {
- None =>
- {
- self.base.resize(idx + 1, 0);
- self.base[idx] = count;
- self.total += count as u64;
- 0
- },
- Some(curr) =>
- {
- let prev = *curr;
- self.total = self.total - prev as u64 + count as u64;
- *curr = count;
- prev
- },
- }
- }
-
- pub fn add(&mut self, ty: item::Type, add: u32, max: u32) -> (u32, u32)
- {
- let idx = u16::from(ty) as usize;
- match self.base.get_mut(idx)
- {
- None =>
- {
- let actual = add.min(max);
- self.base.resize(idx + 1, 0);
- self.base[idx] = actual;
- self.total += add as u64;
- (actual, actual)
- }
- Some(curr) =>
- {
- if *curr < max
- {
- let actual = add.min(max - *curr);
- *curr += actual;
- self.total += actual as u64;
- (actual, *curr)
- }
- else {(0, *curr)}
- },
- }
- }
-
- pub fn try_add(&mut self, ty: item::Type, add: u32, max: u32) -> Result<(u32, u32), TryAddError>
- {
- let idx = u16::from(ty) as usize;
- match self.base.get_mut(idx)
- {
- None =>
- {
- if add <= max
- {
- self.base.resize(idx + 1, 0);
- self.base[idx] = add;
- self.total += add as u64;
- Ok((add, add))
- }
- else {Err(TryAddError{ty, have: 0, add, max})}
- },
- Some(curr) =>
- {
- if *curr <= max && max - *curr <= add
- {
- *curr += add;
- self.total += add as u64;
- Ok((add, *curr))
- }
- else {Err(TryAddError{ty, have: *curr, add, max})}
- },
- }
- }
-
- pub fn sub(&mut self, ty: item::Type, sub: u32, min: u32) -> (u32, u32)
- {
- match self.base.get_mut(u16::from(ty) as usize)
- {
- None => (0, 0),
- Some(curr) =>
- {
- if *curr > min
- {
- let actual = sub.min(*curr - min);
- *curr -= actual;
- self.total -= actual as u64;
- (actual, *curr)
- }
- else {(0, *curr)}
- },
- }
- }
-
- pub fn try_sub(&mut self, ty: item::Type, sub: u32, min: u32) -> Result<(u32, u32), TrySubError>
- {
- let idx = u16::from(ty) as usize;
- match self.base.get_mut(idx)
- {
- None => Err(TrySubError{ty, have: 0, sub, min}),
- Some(curr) =>
- {
- if *curr >= min && *curr - min >= sub
- {
- *curr -= sub;
- self.total -= sub as u64;
- Ok((sub, *curr))
- }
- else {Err(TrySubError{ty, have: *curr, sub, min})}
- },
- }
- }
-
- pub fn add_all(&mut self, other: &Storage, max_each: u32) -> (u64, u64)
- {
- let mut added = 0u64;
- if max_each > 0 && other.get_total() > 0
- {
- let mut iter = other.base.iter().enumerate();
- // resize our vector only once and if necessary
- let (last, add_last) = iter.rfind(|(_, n)| **n != 0).unwrap();
- if self.base.len() <= last
- {
- self.base.resize(last + 1, 0);
- }
- // process items by increasing ID
- for (idx, add) in iter
- {
- let curr = self.base[idx];
- if curr < max_each
- {
- let actual = (*add).min(max_each - curr);
- self.base[idx] += actual;
- added += actual as u64;
- }
- }
- // process the final element (which we've retrieved first)
- let curr = self.base[last];
- if curr < max_each
- {
- let actual = (*add_last).min(max_each - curr);
- self.base[last] += actual;
- added += actual as u64;
- }
- // update total
- self.total += added;
- }
- (added, self.total)
- }
-
- pub fn pull_all(&mut self, other: &mut Storage, max_each: u32) -> (u64, u64, u64)
- {
- let mut added = 0u64;
- if max_each > 0 && other.get_total() > 0
- {
- let mut iter = other.base.iter_mut().enumerate();
- // resize our vector only once and if necessary
- let (last, add_last) = iter.rfind(|(_, n)| **n != 0).unwrap();
- if self.base.len() <= last
- {
- self.base.resize(last + 1, 0);
- }
- // process items by increasing ID
- for (idx, add) in iter
- {
- let curr = self.base[idx];
- if curr < max_each
- {
- let actual = (*add).min(max_each - curr);
- self.base[idx] += actual;
- *add -= actual;
- added += actual as u64;
- }
- }
- // process the final element (which we've retrieved first)
- let curr = self.base[last];
- if curr < max_each
- {
- let actual = (*add_last).min(max_each - curr);
- self.base[last] += actual;
- *add_last -= actual;
- added += actual as u64;
- }
- // update totals
- self.total += added;
- other.total -= added;
- }
- (added, self.total, other.total)
- }
-
- pub fn sub_all(&mut self, other: &Storage, min_each: u32) -> (u64, u64)
- {
- let mut subbed = 0u64;
- if self.get_total() > 0 && other.get_total() > 0
- {
- // no need for resizing, we only remove
- // process items by increasing ID
- for (idx, sub) in other.base.iter().enumerate()
- {
- if let Some(curr) = self.base.get(idx)
- {
- if *curr > min_each
- {
- let actual = (*sub).min(*curr - min_each);
- self.base[idx] -= actual;
- subbed += actual as u64;
- }
- }
- else {break;}
- }
- // update total
- self.total -= subbed;
- }
- (subbed, self.total)
- }
-
- pub fn diff_all(&mut self, other: &mut Storage, min_each: u32) -> (u64, u64, u64)
- {
- let mut subbed = 0u64;
- if self.get_total() > 0 && other.get_total() > 0
- {
- // no need for resizing, we only remove
- // consider only indexes present in both
- let end = self.base.len().min(other.base.len());
- let lhs = &mut self.base[..end];
- let rhs = &mut other.base[..end];
- // process items by increasing ID
- for (l, r) in lhs.into_iter().zip(rhs)
- {
- if *l > min_each && *r > min_each
- {
- let actual = (*l - min_each).min(*r - min_each);
- *l -= actual;
- *r -= actual;
- subbed -= actual as u64;
- }
- }
- // update totals
- self.total -= subbed;
- other.total -= subbed;
- }
- (subbed, self.total, other.total)
- }
-
- pub fn iter<'s>(&'s self) -> Iter<'s>
- {
- Iter{base: self.base.iter().enumerate(), all: true}
- }
-
- pub fn iter_nonzero<'s>(&'s self) -> Iter<'s>
- {
- Iter{base: self.base.iter().enumerate(), all: false}
- }
-
- pub fn clear(&mut self)
- {
- self.base.clear()
- }
+impl Storage {
+ pub const fn new() -> Self {
+ Self {
+ base: Vec::new(),
+ total: 0,
+ }
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.total == 0
+ }
+
+ pub fn get_total(&self) -> u64 {
+ self.total
+ }
+
+ pub fn get(&self, ty: item::Type) -> u32 {
+ match self.base.get(u16::from(ty) as usize) {
+ None => 0,
+ Some(cnt) => *cnt,
+ }
+ }
+
+ pub fn set(&mut self, ty: item::Type, count: u32) -> u32 {
+ let idx = u16::from(ty) as usize;
+ match self.base.get_mut(idx) {
+ None => {
+ self.base.resize(idx + 1, 0);
+ self.base[idx] = count;
+ self.total += count as u64;
+ 0
+ }
+ Some(curr) => {
+ let prev = *curr;
+ self.total = self.total - prev as u64 + count as u64;
+ *curr = count;
+ prev
+ }
+ }
+ }
+
+ pub fn add(&mut self, ty: item::Type, add: u32, max: u32) -> (u32, u32) {
+ let idx = u16::from(ty) as usize;
+ match self.base.get_mut(idx) {
+ None => {
+ let actual = add.min(max);
+ self.base.resize(idx + 1, 0);
+ self.base[idx] = actual;
+ self.total += add as u64;
+ (actual, actual)
+ }
+ Some(curr) => {
+ if *curr < max {
+ let actual = add.min(max - *curr);
+ *curr += actual;
+ self.total += actual as u64;
+ (actual, *curr)
+ } else {
+ (0, *curr)
+ }
+ }
+ }
+ }
+
+ pub fn try_add(
+ &mut self,
+ ty: item::Type,
+ add: u32,
+ max: u32,
+ ) -> Result<(u32, u32), TryAddError> {
+ let idx = u16::from(ty) as usize;
+ match self.base.get_mut(idx) {
+ None => {
+ if add <= max {
+ self.base.resize(idx + 1, 0);
+ self.base[idx] = add;
+ self.total += add as u64;
+ Ok((add, add))
+ } else {
+ Err(TryAddError {
+ ty,
+ have: 0,
+ add,
+ max,
+ })
+ }
+ }
+ Some(curr) => {
+ if *curr <= max && max - *curr <= add {
+ *curr += add;
+ self.total += add as u64;
+ Ok((add, *curr))
+ } else {
+ Err(TryAddError {
+ ty,
+ have: *curr,
+ add,
+ max,
+ })
+ }
+ }
+ }
+ }
+
+ pub fn sub(&mut self, ty: item::Type, sub: u32, min: u32) -> (u32, u32) {
+ match self.base.get_mut(u16::from(ty) as usize) {
+ None => (0, 0),
+ Some(curr) => {
+ if *curr > min {
+ let actual = sub.min(*curr - min);
+ *curr -= actual;
+ self.total -= actual as u64;
+ (actual, *curr)
+ } else {
+ (0, *curr)
+ }
+ }
+ }
+ }
+
+ pub fn try_sub(
+ &mut self,
+ ty: item::Type,
+ sub: u32,
+ min: u32,
+ ) -> Result<(u32, u32), TrySubError> {
+ let idx = u16::from(ty) as usize;
+ match self.base.get_mut(idx) {
+ None => Err(TrySubError {
+ ty,
+ have: 0,
+ sub,
+ min,
+ }),
+ Some(curr) => {
+ if *curr >= min && *curr - min >= sub {
+ *curr -= sub;
+ self.total -= sub as u64;
+ Ok((sub, *curr))
+ } else {
+ Err(TrySubError {
+ ty,
+ have: *curr,
+ sub,
+ min,
+ })
+ }
+ }
+ }
+ }
+
+ pub fn add_all(&mut self, other: &Storage, max_each: u32) -> (u64, u64) {
+ let mut added = 0u64;
+ if max_each > 0 && other.get_total() > 0 {
+ let mut iter = other.base.iter().enumerate();
+ // resize our vector only once and if necessary
+ let (last, add_last) = iter.rfind(|(_, n)| **n != 0).unwrap();
+ if self.base.len() <= last {
+ self.base.resize(last + 1, 0);
+ }
+ // process items by increasing ID
+ for (idx, add) in iter {
+ let curr = self.base[idx];
+ if curr < max_each {
+ let actual = (*add).min(max_each - curr);
+ self.base[idx] += actual;
+ added += actual as u64;
+ }
+ }
+ // process the final element (which we've retrieved first)
+ let curr = self.base[last];
+ if curr < max_each {
+ let actual = (*add_last).min(max_each - curr);
+ self.base[last] += actual;
+ added += actual as u64;
+ }
+ // update total
+ self.total += added;
+ }
+ (added, self.total)
+ }
+
+ pub fn pull_all(&mut self, other: &mut Storage, max_each: u32) -> (u64, u64, u64) {
+ let mut added = 0u64;
+ if max_each > 0 && other.get_total() > 0 {
+ let mut iter = other.base.iter_mut().enumerate();
+ // resize our vector only once and if necessary
+ let (last, add_last) = iter.rfind(|(_, n)| **n != 0).unwrap();
+ if self.base.len() <= last {
+ self.base.resize(last + 1, 0);
+ }
+ // process items by increasing ID
+ for (idx, add) in iter {
+ let curr = self.base[idx];
+ if curr < max_each {
+ let actual = (*add).min(max_each - curr);
+ self.base[idx] += actual;
+ *add -= actual;
+ added += actual as u64;
+ }
+ }
+ // process the final element (which we've retrieved first)
+ let curr = self.base[last];
+ if curr < max_each {
+ let actual = (*add_last).min(max_each - curr);
+ self.base[last] += actual;
+ *add_last -= actual;
+ added += actual as u64;
+ }
+ // update totals
+ self.total += added;
+ other.total -= added;
+ }
+ (added, self.total, other.total)
+ }
+
+ pub fn sub_all(&mut self, other: &Storage, min_each: u32) -> (u64, u64) {
+ let mut subbed = 0u64;
+ if self.get_total() > 0 && other.get_total() > 0 {
+ // no need for resizing, we only remove
+ // process items by increasing ID
+ for (idx, sub) in other.base.iter().enumerate() {
+ if let Some(curr) = self.base.get(idx) {
+ if *curr > min_each {
+ let actual = (*sub).min(*curr - min_each);
+ self.base[idx] -= actual;
+ subbed += actual as u64;
+ }
+ } else {
+ break;
+ }
+ }
+ // update total
+ self.total -= subbed;
+ }
+ (subbed, self.total)
+ }
+
+ pub fn diff_all(&mut self, other: &mut Storage, min_each: u32) -> (u64, u64, u64) {
+ let mut subbed = 0u64;
+ if self.get_total() > 0 && other.get_total() > 0 {
+ // no need for resizing, we only remove
+ // consider only indexes present in both
+ let end = self.base.len().min(other.base.len());
+ let lhs = &mut self.base[..end];
+ let rhs = &mut other.base[..end];
+ // process items by increasing ID
+ for (l, r) in lhs.into_iter().zip(rhs) {
+ if *l > min_each && *r > min_each {
+ let actual = (*l - min_each).min(*r - min_each);
+ *l -= actual;
+ *r -= actual;
+ subbed -= actual as u64;
+ }
+ }
+ // update totals
+ self.total -= subbed;
+ other.total -= subbed;
+ }
+ (subbed, self.total, other.total)
+ }
+
+ pub fn iter<'s>(&'s self) -> Iter<'s> {
+ Iter {
+ base: self.base.iter().enumerate(),
+ all: true,
+ }
+ }
+
+ pub fn iter_nonzero<'s>(&'s self) -> Iter<'s> {
+ Iter {
+ base: self.base.iter().enumerate(),
+ all: false,
+ }
+ }
+
+ pub fn clear(&mut self) {
+ self.base.clear()
+ }
}
// manual because padding with zeros doesn't affect equality
-impl PartialEq for Storage
-{
- fn eq(&self, other: &Self) -> bool
- {
- let mut li = self.base.iter().fuse();
- let mut ri = other.base.iter().fuse();
- loop
- {
- match (li.next(), ri.next())
- {
- (None, None) => return true,
- (l, r) =>
- {
- if l.unwrap_or(&0) != r.unwrap_or(&0) {return false;}
- },
- }
- }
- }
+impl PartialEq for Storage {
+ fn eq(&self, other: &Self) -> bool {
+ let mut li = self.base.iter().fuse();
+ let mut ri = other.base.iter().fuse();
+ loop {
+ match (li.next(), ri.next()) {
+ (None, None) => return true,
+ (l, r) => {
+ if l.unwrap_or(&0) != r.unwrap_or(&0) {
+ return false;
+ }
+ }
+ }
+ }
+ }
}
-impl fmt::Display for Storage
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- let mut first = true;
- for (ty, cnt) in self.iter_nonzero()
- {
- if first {first = false;}
- else {f.write_str(", ")?;}
- write!(f, "{cnt} {ty}")?;
- }
- Ok(())
- }
+impl fmt::Display for Storage {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut first = true;
+ for (ty, cnt) in self.iter_nonzero() {
+ if first {
+ first = false;
+ } else {
+ f.write_str(", ")?;
+ }
+ write!(f, "{cnt} {ty}")?;
+ }
+ Ok(())
+ }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub struct TryAddError
-{
- pub ty: item::Type,
- pub have: u32,
- pub add: u32,
- pub max: u32
+pub struct TryAddError {
+ pub ty: item::Type,
+ pub have: u32,
+ pub add: u32,
+ pub max: u32,
}
-impl fmt::Display for TryAddError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- write!(f, "adding {} {:?} to current {} would exceed {}", self.add, self.ty, self.have, self.max)
- }
+impl fmt::Display for TryAddError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "adding {} {:?} to current {} would exceed {}",
+ self.add, self.ty, self.have, self.max
+ )
+ }
}
impl Error for TryAddError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub struct TrySubError
-{
- pub ty: item::Type,
- pub have: u32,
- pub sub: u32,
- pub min: u32
+pub struct TrySubError {
+ pub ty: item::Type,
+ pub have: u32,
+ pub sub: u32,
+ pub min: u32,
}
-impl fmt::Display for TrySubError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- write!(f, "removing {} {:?} from current {} would drop below {}", self.sub, self.ty, self.have, self.min)
- }
+impl fmt::Display for TrySubError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "removing {} {:?} from current {} would drop below {}",
+ self.sub, self.ty, self.have, self.min
+ )
+ }
}
impl Error for TrySubError {}
#[derive(Clone, Debug)]
-pub struct Iter<'l>
-{
- base: Enumerate<slice::Iter<'l, u32>>,
- all: bool,
+pub struct Iter<'l> {
+ base: Enumerate<slice::Iter<'l, u32>>,
+ all: bool,
}
-impl<'l> Iterator for Iter<'l>
-{
- type Item = (item::Type, u32);
-
- fn next(&mut self) -> Option<Self::Item>
- {
- while let Some((idx, cnt)) = self.base.next()
- {
- if *cnt > 0 || self.all
- {
- if let Ok(ty) = item::Type::try_from(idx as u16)
- {
- return Some((ty, *cnt));
- }
- }
- }
- None
- }
-
- fn size_hint(&self) -> (usize, Option<usize>)
- {
- (0, self.base.size_hint().1)
- }
+impl<'l> Iterator for Iter<'l> {
+ type Item = (item::Type, u32);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ while let Some((idx, cnt)) = self.base.next() {
+ if *cnt > 0 || self.all {
+ if let Ok(ty) = item::Type::try_from(idx as u16) {
+ return Some((ty, *cnt));
+ }
+ }
+ }
+ None
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (0, self.base.size_hint().1)
+ }
}
impl<'l> FusedIterator for Iter<'l> where Enumerate<slice::Iter<'l, u32>>: FusedIterator {}
diff --git a/src/logic/mod.rs b/src/logic/mod.rs
index 1ac1e3b..cd9ba52 100644
--- a/src/logic/mod.rs
+++ b/src/logic/mod.rs
@@ -1,14 +1,13 @@
use crate::content::numeric_enum;
-numeric_enum!
-{
- pub enum LogicField for u8 | TryFromU8Error
- {
- TotalItems, FirstItem, TotalLiquids, TotalPower, ItemCapacity, LiquidCapacity, PowerCapacity, PowerNetCapacity, PowerNetStored, PowerNetIn,
- PowerNetOut, Ammo, AmmoCapacity, Health, MaxHealth, Heat, Efficiency, Progress, Timescale, Rotation, PosX, PosY, ShootX, ShootY, Size, Dead, Range,
- Shooting, Boosting, MineX, MineY, Mining, Speed, Team, Type, Flag, Controlled, Controller, Name, PayloadCount, PayloadType, Enabled, Shoot, ShootP,
- Config, Color
- }
+numeric_enum! {
+ pub enum LogicField for u8 | TryFromU8Error
+ {
+ TotalItems, FirstItem, TotalLiquids, TotalPower, ItemCapacity, LiquidCapacity, PowerCapacity, PowerNetCapacity, PowerNetStored, PowerNetIn,
+ PowerNetOut, Ammo, AmmoCapacity, Health, MaxHealth, Heat, Efficiency, Progress, Timescale, Rotation, PosX, PosY, ShootX, ShootY, Size, Dead, Range,
+ Shooting, Boosting, MineX, MineY, Mining, Speed, Team, Type, Flag, Controlled, Controller, Name, PayloadCount, PayloadType, Enabled, Shoot, ShootP,
+ Config, Color
+ }
}
macro_rules!match_select
@@ -23,18 +22,58 @@ macro_rules!match_select
};
}
-impl LogicField
-{
- pub fn is_readable(&self) -> bool
- {
- match_select!(self, LogicField, TotalItems, FirstItem, TotalLiquids, TotalPower, ItemCapacity, LiquidCapacity, PowerCapacity, PowerNetCapacity,
- PowerNetStored, PowerNetIn, PowerNetOut, Ammo, AmmoCapacity, Health, MaxHealth, Heat, Efficiency, Progress, Timescale, Rotation, PosX, PosY,
- ShootX, ShootY, Size, Dead, Range, Shooting, Boosting, MineX, MineY, Mining, Speed, Team, Type, Flag, Controlled, Controller, Name, PayloadCount,
- PayloadType, Enabled, Color)
- }
-
- pub fn is_writable(&self) -> bool
- {
- match_select!(self, LogicField, Enabled, Shoot, ShootP, Config, Color)
- }
+impl LogicField {
+ pub fn is_readable(&self) -> bool {
+ match_select!(
+ self,
+ LogicField,
+ TotalItems,
+ FirstItem,
+ TotalLiquids,
+ TotalPower,
+ ItemCapacity,
+ LiquidCapacity,
+ PowerCapacity,
+ PowerNetCapacity,
+ PowerNetStored,
+ PowerNetIn,
+ PowerNetOut,
+ Ammo,
+ AmmoCapacity,
+ Health,
+ MaxHealth,
+ Heat,
+ Efficiency,
+ Progress,
+ Timescale,
+ Rotation,
+ PosX,
+ PosY,
+ ShootX,
+ ShootY,
+ Size,
+ Dead,
+ Range,
+ Shooting,
+ Boosting,
+ MineX,
+ MineY,
+ Mining,
+ Speed,
+ Team,
+ Type,
+ Flag,
+ Controlled,
+ Controller,
+ Name,
+ PayloadCount,
+ PayloadType,
+ Enabled,
+ Color
+ )
+ }
+
+ pub fn is_writable(&self) -> bool {
+ match_select!(self, LogicField, Enabled, Shoot, ShootP, Config, Color)
+ }
}
diff --git a/src/modifier.rs b/src/modifier.rs
index cae8761..498b98c 100644
--- a/src/modifier.rs
+++ b/src/modifier.rs
@@ -1,29 +1,28 @@
use crate::content::content_enum;
-content_enum!
-{
- pub enum Type / Modifier for u16 | TryFromU16Error
- {
- None => "none",
- Burning => "burning",
- Freezing => "freezing",
- Unmoving => "unmoving",
- Slow => "slow",
- Wet => "wet",
- Muddy => "muddy",
- Melting => "melting",
- Sapped => "sapped",
- Electrified => "electrified",
- SporeSlowed => "spore-slowed",
- Tarred => "tarred",
- Overdrive => "overdrive",
- Overclock => "overclock",
- Shielded => "shielded",
- Boss => "boss",
- Shocked => "shocked",
- Blasted => "blasted",
- Corroded => "corroded",
- Disarmed => "disarmed",
- Invincible => "invincible",
- }
+content_enum! {
+ pub enum Type / Modifier for u16 | TryFromU16Error
+ {
+ "none",
+ "burning",
+ "freezing",
+ "unmoving",
+ "slow",
+ "wet",
+ "muddy",
+ "melting",
+ "sapped",
+ "electrified",
+ "spore-slowed",
+ "tarred",
+ "overdrive",
+ "overclock",
+ "shielded",
+ "boss",
+ "shocked",
+ "blasted",
+ "corroded",
+ "disarmed",
+ "invincible",
+ }
}
diff --git a/src/registry.rs b/src/registry.rs
index a22682d..240fb65 100644
--- a/src/registry.rs
+++ b/src/registry.rs
@@ -1,50 +1,48 @@
use std::any::type_name;
-use std::collections::HashMap;
use std::collections::hash_map::Entry;
+use std::collections::HashMap;
use std::error::Error;
use std::fmt;
-pub trait RegistryEntry
-{
- fn get_name(&self) -> &str;
+pub trait RegistryEntry {
+ fn get_name(&self) -> &str;
}
-pub struct Registry<'l, E: RegistryEntry + fmt::Debug + 'static>
-{
- by_name: HashMap<&'l str, &'l E>,
+pub struct Registry<'l, E: RegistryEntry + fmt::Debug + 'static> {
+ by_name: HashMap<&'l str, &'l E>,
}
-impl<'l, E: RegistryEntry + fmt::Debug + 'static> Registry<'l, E>
-{
- pub fn new() -> Self
- {
- Self{by_name: HashMap::new()}
- }
-
- pub fn register(&mut self, val: &'l E) -> Result<&'l E, RegisterError<'l, E>>
- {
- match self.by_name.entry(&val.get_name())
- {
- Entry::Occupied(e) => Err(RegisterError(e.get())),
- Entry::Vacant(e) => Ok(e.insert(val)),
- }
- }
-
- pub fn get(&self, name: &str) -> Option<&'l E>
- {
- self.by_name.get(name).map(|&r| r)
- }
+impl<'l, E: RegistryEntry + fmt::Debug + 'static> Registry<'l, E> {
+ pub fn new() -> Self {
+ Self {
+ by_name: HashMap::new(),
+ }
+ }
+
+ pub fn register(&mut self, val: &'l E) -> Result<&'l E, RegisterError<'l, E>> {
+ match self.by_name.entry(&val.get_name()) {
+ Entry::Occupied(e) => Err(RegisterError(e.get())),
+ Entry::Vacant(e) => Ok(e.insert(val)),
+ }
+ }
+
+ pub fn get(&self, name: &str) -> Option<&'l E> {
+ self.by_name.get(name).map(|&r| r)
+ }
}
#[derive(Clone, Copy, Debug)]
pub struct RegisterError<'l, E: RegistryEntry + fmt::Debug + 'static>(pub &'l E);
-impl<'l, E: RegistryEntry + fmt::Debug + 'static> fmt::Display for RegisterError<'l, E>
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- write!(f, "{} {:?} already exists", type_name::<E>(), self.0.get_name())
- }
+impl<'l, E: RegistryEntry + fmt::Debug + 'static> fmt::Display for RegisterError<'l, E> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{} {:?} already exists",
+ type_name::<E>(),
+ self.0.get_name()
+ )
+ }
}
impl<'l, E: RegistryEntry + fmt::Debug + 'static> Error for RegisterError<'l, E> {}
diff --git a/src/team.rs b/src/team.rs
index edd5ec0..d6f32fc 100644
--- a/src/team.rs
+++ b/src/team.rs
@@ -6,129 +6,108 @@ use crate::content::{Content, Type};
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Team(u8);
-impl Team
-{
- pub fn of(id: u8) -> Self
- {
- Self(id)
- }
-
- pub fn is_base(&self) -> bool
- {
- self.0 < 6
- }
+impl Team {
+ pub fn of(id: u8) -> Self {
+ Self(id)
+ }
+
+ pub fn is_base(&self) -> bool {
+ self.0 < 6
+ }
}
-impl From<u8> for Team
-{
- fn from(value: u8) -> Self
- {
- Team::of(value)
- }
+impl From<u8> for Team {
+ fn from(value: u8) -> Self {
+ Team::of(value)
+ }
}
-impl TryFrom<u16> for Team
-{
- type Error = TryFromU16Error;
-
- fn try_from(value: u16) -> Result<Self, Self::Error>
- {
- if value <= u8::MAX as u16 {Ok(Team(value as u8))}
- else {Err(TryFromU16Error(value))}
- }
+impl TryFrom<u16> for Team {
+ type Error = TryFromU16Error;
+
+ fn try_from(value: u16) -> Result<Self, Self::Error> {
+ if value <= u8::MAX as u16 {
+ Ok(Team(value as u8))
+ } else {
+ Err(TryFromU16Error(value))
+ }
+ }
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct TryFromU16Error(pub u16);
-impl From<Team> for u8
-{
- fn from(value: Team) -> Self
- {
- value.0
- }
+impl From<Team> for u8 {
+ fn from(value: Team) -> Self {
+ value.0
+ }
}
-impl From<Team> for u16
-{
- fn from(value: Team) -> Self
- {
- value.0 as u16
- }
+impl From<Team> for u16 {
+ fn from(value: Team) -> Self {
+ value.0 as u16
+ }
}
-impl fmt::Display for Team
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self.0
- {
- 0 => f.write_str("Derelict"),
- 1 => f.write_str("Sharded"),
- 2 => f.write_str("Crux"),
- 3 => f.write_str("Malis"),
- 4 => f.write_str("Green"),
- 5 => f.write_str("Blue"),
- id => write!(f, "Team #{id}"),
- }
- }
+impl fmt::Display for Team {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self.0 {
+ 0 => f.write_str("Derelict"),
+ 1 => f.write_str("Sharded"),
+ 2 => f.write_str("Crux"),
+ 3 => f.write_str("Malis"),
+ 4 => f.write_str("Green"),
+ 5 => f.write_str("Blue"),
+ id => write!(f, "Team #{id}"),
+ }
+ }
}
-impl fmt::Display for TryFromU16Error
-{
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
- {
- write!(f, "no content of type Team for value {}", self.0)
- }
+impl fmt::Display for TryFromU16Error {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "no content of type Team for value {}", self.0)
+ }
}
impl Error for TryFromU16Error {}
const TEAM_NAMES: &str = include_str!("../res/team_names.txt");
-impl Content for Team
-{
- fn get_type(&self) -> Type
- {
- Type::Team
- }
-
- fn get_id(&self) -> u16
- {
- self.0 as u16
- }
-
- fn get_name(&self) -> &'static str
- {
- match self.0
- {
- 0 => "derelict",
- 1 => "sharded",
- 2 => "crux",
- 3 => "malis",
- 4 => "green",
- 5 => "blue",
- // dark magic: offsets manually computed, then rely on the format "...|team#{i}|..."
- i @ 6..=9 =>
- {
- // length: 7 ("team#" (5) + 1 digit + "|" (1))
- let s = 0 + ((i - 6) as usize) * 7;
- &TEAM_NAMES[s..s + 6] // exclude the trailing "|"
- },
- i @ 10..=99 =>
- {
- // length: 8 ("team#" (5) + 2 digits + "|" (1))
- let s = 28 + ((i - 10) as usize) * 8;
- &TEAM_NAMES[s..s + 7] // exclude the trailing "|"
- },
- i @ 100..=255 =>
- {
- // length: 9 ("team#" (5) + 3 digits + "|" (1))
- let s = 748 + ((i - 100) as usize) * 9;
- &TEAM_NAMES[s..s + 8] // exclude the trailing "|"
- },
- }
- }
+impl Content for Team {
+ fn get_type(&self) -> Type {
+ Type::Team
+ }
+
+ fn get_id(&self) -> u16 {
+ self.0 as u16
+ }
+
+ fn get_name(&self) -> &'static str {
+ match self.0 {
+ 0 => "derelict",
+ 1 => "sharded",
+ 2 => "crux",
+ 3 => "malis",
+ 4 => "green",
+ 5 => "blue",
+ // dark magic: offsets manually computed, then rely on the format "...|team#{i}|..."
+ i @ 6..=9 => {
+ // length: 7 ("team#" (5) + 1 digit + "|" (1))
+ let s = 0 + ((i - 6) as usize) * 7;
+ &TEAM_NAMES[s..s + 6] // exclude the trailing "|"
+ }
+ i @ 10..=99 => {
+ // length: 8 ("team#" (5) + 2 digits + "|" (1))
+ let s = 28 + ((i - 10) as usize) * 8;
+ &TEAM_NAMES[s..s + 7] // exclude the trailing "|"
+ }
+ i @ 100..=255 => {
+ // length: 9 ("team#" (5) + 3 digits + "|" (1))
+ let s = 748 + ((i - 100) as usize) * 9;
+ &TEAM_NAMES[s..s + 8] // exclude the trailing "|"
+ }
+ }
+ }
}
pub const DERELICT: Team = Team(0);
diff --git a/src/unit/mod.rs b/src/unit/mod.rs
index 43b1e3e..c063d2a 100644
--- a/src/unit/mod.rs
+++ b/src/unit/mod.rs
@@ -1,73 +1,72 @@
use crate::content::content_enum;
-content_enum!
-{
- pub enum Type / Unit for u16 | TryFromU16Error
- {
- Dagger => "dagger",
- Mace => "mace",
- Fortress => "fortress",
- Scepter => "scepter",
- Reign => "reign",
- Nova => "nova",
- Pulsar => "pulsar",
- Quasar => "quasar",
- Vela => "vela",
- Corvus => "corvus",
- Crawler => "crawler",
- Atrax => "atrax",
- Spiroct => "spiroct",
- Arkyid => "arkyid",
- Toxopid => "toxopid",
- Flare => "flare",
- Horizon => "horizon",
- Zenith => "zenith",
- Antumbra => "antumbra",
- Eclipse => "eclipse",
- Mono => "mono",
- Poly => "poly",
- Mega => "mega",
- Quad => "quad",
- Oct => "oct",
- Risso => "risso",
- Minke => "minke",
- Bryde => "bryde",
- Sei => "sei",
- Omura => "omura",
- Retusa => "retusa",
- Oxynoe => "oxynoe",
- Cyerce => "cyerce",
- Aegires => "aegires",
- Navanax => "navanax",
- Alpha => "alpha",
- Beta => "beta",
- Gamma => "gamma",
- Stell => "stell",
- Locus => "locus",
- Precept => "precept",
- Vanquish => "vanquish",
- Conquer => "conquer",
- Merui => "merui",
- Cleroi => "cleroi",
- Anthicus => "anthicus",
- AnthicusMissile => "anthicus-missile",
- Tecta => "tecta",
- Collaris => "collaris",
- Elude => "elude",
- Avert => "avert",
- Obviate => "obviate",
- Quell => "quell",
- QuellMissile => "quell-missile",
- Disrupt => "disrupt",
- DisruptMissile => "disrupt-missile",
- Renale => "renale",
- Latum => "latum",
- Evoke => "evoke",
- Incite => "incite",
- Emanate => "emanate",
- Block => "block",
- Manifold => "manifold",
- AssemblyDrone => "assembly-drone",
- ScatheMissile => "scathe-missile",
- }
+content_enum! {
+ pub enum Type / Unit for u16 | TryFromU16Error
+ {
+ "dagger",
+ "mace",
+ "fortress",
+ "scepter",
+ "reign",
+ "nova",
+ "pulsar",
+ "quasar",
+ "vela",
+ "corvus",
+ "crawler",
+ "atrax",
+ "spiroct",
+ "arkyid",
+ "toxopid",
+ "flare",
+ "horizon",
+ "zenith",
+ "antumbra",
+ "eclipse",
+ "mono",
+ "poly",
+ "mega",
+ "quad",
+ "oct",
+ "risso",
+ "minke",
+ "bryde",
+ "sei",
+ "omura",
+ "retusa",
+ "oxynoe",
+ "cyerce",
+ "aegires",
+ "navanax",
+ "alpha",
+ "beta",
+ "gamma",
+ "stell",
+ "locus",
+ "precept",
+ "vanquish",
+ "conquer",
+ "merui",
+ "cleroi",
+ "anthicus",
+ "anthicus-missile",
+ "tecta",
+ "collaris",
+ "elude",
+ "avert",
+ "obviate",
+ "quell",
+ "quell-missile",
+ "disrupt",
+ "disrupt-missile",
+ "renale",
+ "latum",
+ "evoke",
+ "incite",
+ "emanate",
+ "block",
+ "manifold",
+ "assembly-drone",
+ "scathe-missile",
+ }
}
diff --git a/src/utils/once_cell.rs b/src/utils/once_cell.rs
index a5e72f7..4bfd3eb 100644
--- a/src/utils/once_cell.rs
+++ b/src/utils/once_cell.rs
@@ -7,164 +7,160 @@ const STATE_INIT: u8 = 0;
const STATE_LOCKED: u8 = STATE_INIT + 1;
const STATE_READY: u8 = STATE_LOCKED + 1;
-pub struct OnceCell<T>
-{
- value: UnsafeCell<MaybeUninit<T>>,
- state: AtomicU8,
+pub struct OnceCell<T> {
+ value: UnsafeCell<MaybeUninit<T>>,
+ state: AtomicU8,
}
-impl<T> OnceCell<T>
-{
- pub const fn new() -> Self
- {
- Self{value: UnsafeCell::new(MaybeUninit::uninit()), state: AtomicU8::new(STATE_INIT)}
- }
-
- pub const fn new_init(value: T) -> Self
- {
- Self{value: UnsafeCell::new(MaybeUninit::new(value)), state: AtomicU8::new(STATE_READY)}
- }
-
- pub fn get(&self) -> Option<&T>
- {
- if self.state.load(Ordering::Acquire) == STATE_READY
- {
- // SAFETY: won't be overwritten for the lifetime of this reference
- Some(unsafe{(&*self.value.get()).assume_init_ref()})
- }
- else {None}
- }
-
- pub fn get_or_wait(&self) -> Option<&T>
- {
- loop
- {
- match self.state.load(Ordering::Acquire)
- {
- STATE_INIT => return None,
- STATE_LOCKED => (), // continue
- STATE_READY => return Some(unsafe{(&*self.value.get()).assume_init_ref()}),
- x => unreachable!("invalid state {x}"),
- }
- }
- }
-
- pub fn get_or_init<F: Fn() -> T>(&self, init: F) -> &T
- {
- loop
- {
- match self.state.compare_exchange(STATE_INIT, STATE_LOCKED, Ordering::AcqRel, Ordering::Acquire)
- {
- Ok(..) =>
- {
- let value = init();
- let written = &*unsafe{&mut *self.value.get()}.write(value);
- self.state.store(STATE_READY, Ordering::Release);
- return written;
- },
- Err(STATE_READY) => return unsafe{(&*self.value.get()).assume_init_ref()},
- Err(..) => (), // locked or spurious failure
- }
- }
- }
-
- pub fn set(&self, value: T) -> Result<&T, T>
- {
- // don't set state to STATE_READY on success because we have to release afterward anyway
- match self.state.compare_exchange(STATE_INIT, STATE_LOCKED, Ordering::AcqRel, Ordering::Acquire)
- {
- Ok(..) =>
- {
- // SAFETY: unique because only one thread can lock the atomic state
- let written = &*unsafe{&mut *self.value.get()}.write(value);
- self.state.store(STATE_READY, Ordering::Release);
- Ok(written)
- },
- // SAFETY: guaranteed to be initialized & protected by acquire ordering
- Err(STATE_READY) => return Ok(unsafe{(&*self.value.get()).assume_init_ref()}),
- Err(..) => Err(value), // locked or spurious failure
- }
- }
-
- pub fn set_mut(&mut self, mut value: T) -> Result<Option<T>, T>
- {
- // don't set state to STATE_READY on success because we have to release afterward anyway
- match self.state.compare_exchange(STATE_INIT, STATE_LOCKED, Ordering::AcqRel, Ordering::Acquire)
- {
- Ok(..) =>
- {
- self.value.get_mut().write(value);
- self.state.store(STATE_READY, Ordering::Release);
- Ok(None)
- },
- Err(STATE_READY) =>
- {
- // SAFETY: guaranteed to be initialized & protected by acquire ordering
- std::mem::swap(unsafe{self.value.get_mut().assume_init_mut()}, &mut value);
- // ensure changes are visible to others acquiring the atomic state
- self.state.store(STATE_READY, Ordering::Release);
- // we've swapped the previous value into this variable
- Ok(Some(value))
- },
- Err(..) => Err(value), // locked or spurious failure
- }
- }
-
- pub fn into_inner(mut self) -> Option<T>
- {
- // must be atomic so potential writes during the drop see a valid state
- let inner = match self.state.load(Ordering::Acquire)
- {
- STATE_INIT => None,
- STATE_LOCKED => unreachable!("consumed cell during initialization"),
- // SAFETY: initialized & we'll forget about it afterwards
- STATE_READY => Some(unsafe{self.value.get_mut().assume_init_read()}),
- x => unreachable!("invalid state {x}"),
- };
- // SAFETY: just in case AtomicU8 has a drop handler
- unsafe{ptr::drop_in_place(&mut self.state as *mut _);}
- std::mem::forget(self);
- inner
- }
+impl<T> OnceCell<T> {
+ pub const fn new() -> Self {
+ Self {
+ value: UnsafeCell::new(MaybeUninit::uninit()),
+ state: AtomicU8::new(STATE_INIT),
+ }
+ }
+
+ pub const fn new_init(value: T) -> Self {
+ Self {
+ value: UnsafeCell::new(MaybeUninit::new(value)),
+ state: AtomicU8::new(STATE_READY),
+ }
+ }
+
+ pub fn get(&self) -> Option<&T> {
+ if self.state.load(Ordering::Acquire) == STATE_READY {
+ // SAFETY: won't be overwritten for the lifetime of this reference
+ Some(unsafe { (&*self.value.get()).assume_init_ref() })
+ } else {
+ None
+ }
+ }
+
+ pub fn get_or_wait(&self) -> Option<&T> {
+ loop {
+ match self.state.load(Ordering::Acquire) {
+ STATE_INIT => return None,
+ STATE_LOCKED => (), // continue
+ STATE_READY => return Some(unsafe { (&*self.value.get()).assume_init_ref() }),
+ x => unreachable!("invalid state {x}"),
+ }
+ }
+ }
+
+ pub fn get_or_init<F: Fn() -> T>(&self, init: F) -> &T {
+ loop {
+ match self.state.compare_exchange(
+ STATE_INIT,
+ STATE_LOCKED,
+ Ordering::AcqRel,
+ Ordering::Acquire,
+ ) {
+ Ok(..) => {
+ let value = init();
+ let written = &*unsafe { &mut *self.value.get() }.write(value);
+ self.state.store(STATE_READY, Ordering::Release);
+ return written;
+ }
+ Err(STATE_READY) => return unsafe { (&*self.value.get()).assume_init_ref() },
+ Err(..) => (), // locked or spurious failure
+ }
+ }
+ }
+
+ pub fn set(&self, value: T) -> Result<&T, T> {
+ // don't set state to STATE_READY on success because we have to release afterward anyway
+ match self.state.compare_exchange(
+ STATE_INIT,
+ STATE_LOCKED,
+ Ordering::AcqRel,
+ Ordering::Acquire,
+ ) {
+ Ok(..) => {
+ // SAFETY: unique because only one thread can lock the atomic state
+ let written = &*unsafe { &mut *self.value.get() }.write(value);
+ self.state.store(STATE_READY, Ordering::Release);
+ Ok(written)
+ }
+ // SAFETY: guaranteed to be initialized & protected by acquire ordering
+ Err(STATE_READY) => return Ok(unsafe { (&*self.value.get()).assume_init_ref() }),
+ Err(..) => Err(value), // locked or spurious failure
+ }
+ }
+
+ pub fn set_mut(&mut self, mut value: T) -> Result<Option<T>, T> {
+ // don't set state to STATE_READY on success because we have to release afterward anyway
+ match self.state.compare_exchange(
+ STATE_INIT,
+ STATE_LOCKED,
+ Ordering::AcqRel,
+ Ordering::Acquire,
+ ) {
+ Ok(..) => {
+ self.value.get_mut().write(value);
+ self.state.store(STATE_READY, Ordering::Release);
+ Ok(None)
+ }
+ Err(STATE_READY) => {
+ // SAFETY: guaranteed to be initialized & protected by acquire ordering
+ std::mem::swap(
+ unsafe { self.value.get_mut().assume_init_mut() },
+ &mut value,
+ );
+ // ensure changes are visible to others acquiring the atomic state
+ self.state.store(STATE_READY, Ordering::Release);
+ // we've swapped the previous value into this variable
+ Ok(Some(value))
+ }
+ Err(..) => Err(value), // locked or spurious failure
+ }
+ }
+
+ pub fn into_inner(mut self) -> Option<T> {
+ // must be atomic so potential writes during the drop see a valid state
+ let inner = match self.state.load(Ordering::Acquire) {
+ STATE_INIT => None,
+ STATE_LOCKED => unreachable!("consumed cell during initialization"),
+ // SAFETY: initialized & we'll forget about it afterwards
+ STATE_READY => Some(unsafe { self.value.get_mut().assume_init_read() }),
+ x => unreachable!("invalid state {x}"),
+ };
+ // SAFETY: just in case AtomicU8 has a drop handler
+ unsafe {
+ ptr::drop_in_place(&mut self.state as *mut _);
+ }
+ std::mem::forget(self);
+ inner
+ }
}
-impl<T> Default for OnceCell<T>
-{
- fn default() -> Self
- {
- OnceCell::new()
- }
+impl<T> Default for OnceCell<T> {
+ fn default() -> Self {
+ OnceCell::new()
+ }
}
-impl<T> From<T> for OnceCell<T>
-{
- fn from(value: T) -> Self
- {
- OnceCell::new_init(value)
- }
+impl<T> From<T> for OnceCell<T> {
+ fn from(value: T) -> Self {
+ OnceCell::new_init(value)
+ }
}
-impl<T> From<OnceCell<T>> for Option<T>
-{
- fn from(value: OnceCell<T>) -> Self
- {
- value.into_inner()
- }
+impl<T> From<OnceCell<T>> for Option<T> {
+ fn from(value: OnceCell<T>) -> Self {
+ value.into_inner()
+ }
}
-impl<T> Drop for OnceCell<T>
-{
- fn drop(&mut self)
- {
- match *self.state.get_mut()
- {
- STATE_INIT => (),
- STATE_LOCKED => unreachable!("dropped cell during initialization"),
- // MaybeUninit requires us to manually drop the value
- STATE_READY => unsafe{self.value.get_mut().assume_init_drop()},
- x => unreachable!("invalid state {x}"),
- }
- }
+impl<T> Drop for OnceCell<T> {
+ fn drop(&mut self) {
+ match *self.state.get_mut() {
+ STATE_INIT => (),
+ STATE_LOCKED => unreachable!("dropped cell during initialization"),
+ // MaybeUninit requires us to manually drop the value
+ STATE_READY => unsafe { self.value.get_mut().assume_init_drop() },
+ x => unreachable!("invalid state {x}"),
+ }
+ }
}
unsafe impl<T: Send> Send for OnceCell<T> {}