mindustry logic execution, map- and schematic- parsing and rendering
cooler macros
34 files changed, 7671 insertions, 7893 deletions
@@ -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(®); - let mut state = State{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(®); + let mut state = State { + 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(®); - 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(®); + 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> {} |