diff --git a/src/sandbox/mod.rs b/src/sandbox/mod.rs index 946161c..18b83bc 100644 --- a/src/sandbox/mod.rs +++ b/src/sandbox/mod.rs @@ -26,17 +26,22 @@ pub struct Sandbox { impl Sandbox { /// Create a new, empty sandbox level. pub fn new() -> Self { + Self::with_meta_info(default_component_types_map(), Vec::new()) + } + + /// Create an empty sandbox level with custom component-type / mod info. + fn with_meta_info(component_types: HashMap, mods: Vec) -> Self { Self { next_component: 1, next_cluster: 0, next_wire: 0, - next_type: 0, + next_type: component_types.values().copied().max().unwrap_or(0), root_components: HashSet::new(), components: HashMap::new(), wires: HashMap::new(), clusters: HashMap::new(), - component_types: default_component_types_map(), - mods: Vec::new(), + component_types, + mods, } } @@ -61,46 +66,95 @@ impl Sandbox { .collect(), custom_data: component.custom_data.clone(), }; - let id = ComponentId(NonZeroU32::new(self.next_component).unwrap()); - self.next_component += 1; + self.insert_component(id, info); + id + } - self.components.insert(id, info); - if let Some(parent) = component.parent { + /// Internal logic of `add_component` that is shared with savefile loading. + /// Savefiles store the component and cluster IDs, so they can be directly + /// passed instead of being allocated (as is done by `add_component`). + fn insert_component(&mut self, id: ComponentId, info: ComponentInfo) { + // TODO bubble this error (could be triggered by an invalid savefile). + assert!(!self.components.contains_key(&id)); + + // Update component ID allocator. + // TODO make this less sparse, 2 billion is not very much to work with + // over the lifetime of a world. + self.next_component = self.next_component.max(id.into_raw() + 1); + + // Add parent-child cross-reference. + if let Some(parent) = info.parent { + // Valid savefiles will store and load the parent before the child, + // so it is reasonable to assume the parent exists here. + //TODO bubble this error; it should be recoverable in file loading. self.components .get_mut(&parent) .unwrap() .children .insert(id); + } else { + self.root_components.insert(id); } - id + // Add component info. + self.components.insert(id, info); } pub fn add_wire( &mut self, addr_a: PegAddress, addr_b: PegAddress, + rotation: f32, + ) -> Result { + self.insert_wire(addr_a, addr_b, rotation, None) + } + + /// Like the `add_component` / `insert_component` duality, this has logic + /// shared between `add_wire` and savefile loading. Unlike + /// `insert_component`, this does allocate the wire ID automatically, + /// because the save file doesn't use wire IDs. + fn insert_wire( + &mut self, + addr_a: PegAddress, + addr_b: PegAddress, + rotation: f32, + cluster_id: Option, ) -> Result { // It is illegal to directly connect output pegs. if addr_a.peg_type == PegType::Output && addr_b.peg_type == PegType::Output { return Err(AddWireError::InvalidPegAddress)?; } - // If there is already a wire connecting these pegs, nothing needs to be - // done. let peg_a = self .get_peg(&addr_a) .ok_or(AddWireError::InvalidPegAddress)?; let peg_b = self .get_peg(&addr_b) .ok_or(AddWireError::InvalidPegAddress)?; + // If there is already a wire connecting these pegs, nothing needs to be + // done. if let Some(&wire_id) = peg_a.wires.intersection(&peg_b.wires).next() { return Ok(wire_id); } - // Ensure both pegs have the same cluster. - let cluster_id = self.merge_clusters(peg_a.cluster_id, peg_b.cluster_id); + let cluster_id = match cluster_id { + Some(id) => { + // If cluster ID is specified (i.e. from an existing savefile), + // check savefile validity: ensure that both endpoints and the + // wire all have the same cluster. + + //TODO bubble this error + assert!(id == peg_a.cluster_id && id == peg_b.cluster_id); + + id + } + None => { + // If no cluster ID is given (i.e. caller is `add_wire`), then + // obtain one from merging the two endpoints. + self.merge_clusters(peg_a.cluster_id, peg_b.cluster_id) + } + }; // Create wire. let wire_id = WireId(self.next_wire); @@ -111,7 +165,7 @@ impl Sandbox { a: addr_a, b: addr_b, cluster_id, - rotation: 0.0, + rotation, }, ); @@ -265,38 +319,47 @@ impl<'a> ComponentBuilder<'a> { } } -#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ComponentId(NonZeroU32); impl ComponentId { fn into_raw(self) -> u32 { self.0.into() } + + fn from_raw(raw: u32) -> Option { + NonZeroU32::new(raw).map(Self) + } } -#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ClusterId(u32); impl ClusterId { fn into_raw(self) -> i32 { self.0.try_into().unwrap() } + + fn from_raw(raw: i32) -> Self { + Self(raw.try_into().unwrap()) + } } -#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct WireId(u32); +#[derive(Debug)] pub enum AddWireError { InvalidPegAddress, } -#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum PegType { Input, Output, } -#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct PegAddress { pub component: ComponentId, pub peg_type: PegType, diff --git a/src/sandbox/serialize.rs b/src/sandbox/serialize.rs index 2b929db..2e24a98 100644 --- a/src/sandbox/serialize.rs +++ b/src/sandbox/serialize.rs @@ -1,6 +1,7 @@ //! Conversions between the Sandbox editor and Blotter serialization types. use crate::latest as blotter; +use std::collections::HashSet; /// The current game version that this save is compatible with. const GAME_VERSION: [i32; 4] = [0, 91, 0, 510]; @@ -43,6 +44,40 @@ impl From<&super::Sandbox> for blotter::BlotterFile { } } +impl From<&blotter::BlotterFile> for super::Sandbox { + fn from(file: &blotter::BlotterFile) -> Self { + // Instead of building the sandbox and all the internal cross-references + // from scratch, re-use the sandbox API as much as possible when loading + // so there is just one implementation of the cross-referencing. + let mut sandbox = super::Sandbox::with_meta_info( + file.component_types + .iter() + .map(|ctype| (ctype.text_id.clone(), ctype.numeric_id)) + .collect(), + file.mods.clone(), + ); + for component in &file.components { + let (id, info) = component.into(); + sandbox.insert_component(id, info); + } + for wire in &file.wires { + // TODO bubble error + sandbox + .insert_wire( + wire.start_peg.into(), + wire.end_peg.into(), + wire.rotation, + Some(super::ClusterId::from_raw(wire.circuit_state_id)), + ) + .unwrap(); + } + + //TODO load circuit states + + sandbox + } +} + impl From<(super::ComponentId, &super::ComponentInfo)> for blotter::Component { fn from((id, component): (super::ComponentId, &super::ComponentInfo)) -> Self { Self { @@ -58,6 +93,27 @@ impl From<(super::ComponentId, &super::ComponentInfo)> for blotter::Component { } } +impl From<&blotter::Component> for (super::ComponentId, super::ComponentInfo) { + fn from(component: &blotter::Component) -> Self { + let id = super::ComponentId::from_raw(component.address).unwrap(); + let info = super::ComponentInfo { + type_id: component.type_id, + parent: super::ComponentId::from_raw(component.parent), + position: component.position, + rotation: component.rotation, + children: HashSet::new(), + inputs: component.inputs.iter().map(|input| input.into()).collect(), + outputs: component + .outputs + .iter() + .map(|output| output.into()) + .collect(), + custom_data: component.custom_data.clone(), + }; + (id, info) + } +} + impl From<&super::PegInfo> for blotter::Input { fn from(peg: &super::PegInfo) -> Self { Self { @@ -66,6 +122,15 @@ impl From<&super::PegInfo> for blotter::Input { } } +impl From<&blotter::Input> for super::PegInfo { + fn from(input: &blotter::Input) -> Self { + Self { + cluster_id: super::ClusterId::from_raw(input.circuit_state_id), + wires: HashSet::new(), + } + } +} + impl From<&super::PegInfo> for blotter::Output { fn from(peg: &super::PegInfo) -> Self { Self { @@ -74,6 +139,15 @@ impl From<&super::PegInfo> for blotter::Output { } } +impl From<&blotter::Output> for super::PegInfo { + fn from(input: &blotter::Output) -> Self { + Self { + cluster_id: super::ClusterId::from_raw(input.circuit_state_id), + wires: HashSet::new(), + } + } +} + impl From<&super::WireInfo> for blotter::Wire { fn from(wire: &super::WireInfo) -> Self { Self { @@ -95,6 +169,17 @@ impl From for blotter::PegAddress { } } +impl From for super::PegAddress { + fn from(addr: blotter::PegAddress) -> Self { + //TODO bubble errors + Self { + component: super::ComponentId::from_raw(addr.component_address).unwrap(), + peg_type: addr.peg_type.into(), + peg_index: addr.peg_index.try_into().unwrap(), + } + } +} + impl From for blotter::PegType { fn from(peg_type: super::PegType) -> Self { match peg_type { @@ -103,3 +188,12 @@ impl From for blotter::PegType { } } } + +impl From for super::PegType { + fn from(peg_type: blotter::PegType) -> Self { + match peg_type { + blotter::PegType::Input => Self::Input, + blotter::PegType::Output => Self::Output, + } + } +} diff --git a/src/v5.rs b/src/v5.rs index 65ecc0a..850c694 100644 --- a/src/v5.rs +++ b/src/v5.rs @@ -197,7 +197,7 @@ impl WriteTo for Component { } } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum PegType { Output, Input, @@ -225,7 +225,7 @@ impl WriteTo for PegType { } } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct PegAddress { pub peg_type: PegType, pub component_address: u32,