From 21a9011c447175f7a0e734a486d617fb0ffc0a7b Mon Sep 17 00:00:00 2001 From: Adam Gausmann Date: Sun, 6 Nov 2022 12:18:52 -0600 Subject: [PATCH] Add Sandbox editor with add_component/add_wire transactions --- src/lib.rs | 1 + src/sandbox/mod.rs | 394 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 395 insertions(+) create mode 100644 src/sandbox/mod.rs diff --git a/src/lib.rs b/src/lib.rs index 418d3c4..1ef9ff7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ mod convert; pub mod custom_data; pub mod error; pub(crate) mod io; +pub mod sandbox; pub mod v5; pub mod v6; diff --git a/src/sandbox/mod.rs b/src/sandbox/mod.rs new file mode 100644 index 0000000..9720b47 --- /dev/null +++ b/src/sandbox/mod.rs @@ -0,0 +1,394 @@ +//! High-level API for constructing and modifying a "Sandbox" / World. + +use crate::latest as blotter; +use std::{ + collections::{HashMap, HashSet}, + iter::repeat_with, + num::NonZeroU32, +}; + +/// An in-memory representation of a Sandbox that is easy to modify. +pub struct Sandbox { + next_component: u32, + next_cluster: u32, + next_wire: u32, + next_type: u16, + root_components: HashSet, + components: HashMap, + wires: HashMap, + clusters: HashMap, + component_types: HashMap, + mods: Vec, +} + +impl Sandbox { + /// Create a new, empty sandbox level. + pub fn new() -> Self { + Self { + next_component: 1, + next_cluster: 0, + next_wire: 0, + next_type: 0, + root_components: HashSet::new(), + components: HashMap::new(), + wires: HashMap::new(), + clusters: HashMap::new(), + component_types: default_component_types_map(), + mods: Vec::new(), + } + } + + pub fn add_component(&mut self, component: &ComponentBuilder) -> ComponentId { + let info = ComponentInfo { + type_id: self.get_component_type(component.id), + parent: component.parent, + position: component.position, + rotation: component.rotation, + children: HashSet::new(), + inputs: repeat_with(|| PegInfo { + cluster_id: self.make_cluster(), + wires: HashSet::new(), + }) + .take(component.num_inputs as usize) + .collect(), + outputs: repeat_with(|| PegInfo { + cluster_id: self.make_cluster(), + wires: HashSet::new(), + }) + .take(component.num_outputs as usize) + .collect(), + custom_data: component.custom_data.clone(), + }; + + let id = ComponentId(NonZeroU32::new(self.next_component).unwrap()); + self.next_component += 1; + + self.components.insert(id, info); + if let Some(parent) = component.parent { + self.components + .get_mut(&parent) + .unwrap() + .children + .insert(id); + } + + id + } + + pub fn add_wire( + &mut self, + addr_a: PegAddress, + addr_b: PegAddress, + ) -> 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 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); + + // Create wire. + let wire_id = WireId(self.next_wire); + self.next_wire += 1; + self.wires.insert( + wire_id, + WireInfo { + a: addr_a, + b: addr_b, + cluster_id, + rotation: 0.0, + }, + ); + + // Register wire references in pegs and cluster. + self.get_peg_mut(&addr_a).unwrap().wires.insert(wire_id); + self.get_peg_mut(&addr_b).unwrap().wires.insert(wire_id); + self.clusters + .get_mut(&cluster_id) + .unwrap() + .wires + .insert(wire_id); + + Ok(wire_id) + } + + pub fn remove_component(&mut self, id: ComponentId) { + // TODO Remove all wires connected to this component. + + // TODO Remove component. + + // TODO Remove peg-cluster cross-references. Remove clusters if empty. + + todo!() + } + + pub fn remove_wire(&mut self, id: WireId) { + // TODO Remove wire-cluster cross-reference. Remove cluster if empty. + + // TODO Remove wire. + + // TODO Split cluster if necessary. + + todo!() + } + + fn get_component_type(&mut self, id: &str) -> u16 { + match self.component_types.get(id) { + Some(&x) => x, + None => { + let num = self.next_type; + self.next_type += 1; + self.component_types.insert(id.to_owned(), num); + num + } + } + } + + fn make_cluster(&mut self) -> ClusterId { + let id = ClusterId(self.next_cluster); + self.next_cluster += 1; + id + } + + fn merge_clusters(&mut self, id_a: ClusterId, id_b: ClusterId) -> ClusterId { + // Nothing needs to be done if the two clusters are the same. + if id_a == id_b { + return id_a; + } + + let a = &self.clusters[&id_a]; + let b = &self.clusters[&id_b]; + // Merge the smaller cluster into the larger cluster. + let (id_dest, id_src) = if a.size() >= b.size() { + (id_a, id_b) + } else { + (id_b, id_a) + }; + + let src = self.clusters.remove(&id_src).unwrap(); + // Update cluster cross-references: + for wire_id in &src.wires { + self.wires.get_mut(wire_id).unwrap().cluster_id = id_dest; + } + for peg_id in &src.pegs { + self.get_peg_mut(peg_id).unwrap().cluster_id = id_dest; + } + + // Move references from src into dest: + let dest = self.clusters.get_mut(&id_dest).unwrap(); + dest.pegs.extend(src.pegs); + dest.wires.extend(src.wires); + + id_dest + } + + fn get_peg(&self, addr: &PegAddress) -> Option<&PegInfo> { + self.components + .get(&addr.component) + .and_then(|component| component.get_peg(addr)) + } + + fn get_peg_mut(&mut self, addr: &PegAddress) -> Option<&mut PegInfo> { + self.components + .get_mut(&addr.component) + .and_then(|component| component.get_peg_mut(addr)) + } +} + +#[derive(Clone)] +pub struct ComponentBuilder<'a> { + id: &'a str, + parent: Option, + position: [i32; 3], + rotation: [f32; 4], + num_inputs: u32, + num_outputs: u32, + custom_data: Option>, +} + +impl<'a> ComponentBuilder<'a> { + pub fn new(id: &'a str) -> Self { + Self { + id, + parent: None, + position: [0; 3], + rotation: [0.0, 0.0, 0.0, 1.0], + num_inputs: 0, + num_outputs: 0, + custom_data: None, + } + } + + pub fn parent(self, parent: Option) -> Self { + Self { parent, ..self } + } + + pub fn position(self, position: [i32; 3]) -> Self { + Self { position, ..self } + } + + pub fn rotation(self, rotation: [f32; 4]) -> Self { + Self { rotation, ..self } + } + + pub fn num_inputs(self, num_inputs: u32) -> Self { + Self { num_inputs, ..self } + } + + pub fn num_outputs(self, num_outputs: u32) -> Self { + Self { + num_outputs, + ..self + } + } + + pub fn custom_data(self, custom_data: Option>) -> Self { + Self { + custom_data, + ..self + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct ComponentId(NonZeroU32); + +impl ComponentId { + fn into_raw(self) -> u32 { + self.0.into() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct ClusterId(u32); + +impl ClusterId { + fn into_raw(self) -> i32 { + self.0.try_into().unwrap() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct WireId(u32); + +pub enum AddWireError { + InvalidPegAddress, +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub enum PegType { + Input, + Output, +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct PegAddress { + pub component: ComponentId, + pub peg_type: PegType, + pub peg_index: usize, +} + +struct ComponentInfo { + type_id: u16, + parent: Option, + position: [i32; 3], + rotation: [f32; 4], + children: HashSet, + inputs: Vec, + outputs: Vec, + custom_data: Option>, +} + +impl ComponentInfo { + fn get_peg(&self, addr: &PegAddress) -> Option<&PegInfo> { + match addr.peg_type { + PegType::Input => self.inputs.get(addr.peg_index), + PegType::Output => self.outputs.get(addr.peg_index), + } + } + + fn get_peg_mut(&mut self, addr: &PegAddress) -> Option<&mut PegInfo> { + match addr.peg_type { + PegType::Input => self.inputs.get_mut(addr.peg_index), + PegType::Output => self.outputs.get_mut(addr.peg_index), + } + } +} + +struct PegInfo { + cluster_id: ClusterId, + wires: HashSet, +} + +#[derive(Clone, Copy)] +struct WireInfo { + a: PegAddress, + b: PegAddress, + cluster_id: ClusterId, + rotation: f32, +} + +struct ClusterInfo { + wires: HashSet, + pegs: HashSet, +} + +impl ClusterInfo { + fn size(&self) -> usize { + self.wires.len() + self.pegs.len() + } +} + +const DEFAULT_COMPONENT_TYPES: [(u16, &str); 31] = [ + (0, "MHG.Inverter"), + (1, "MHG.XorGate"), + (2, "MHG.AndGate"), + (3, "MHG.Delayer"), + (4, "MHG.DLatch"), + (5, "MHG.Randomizer"), + (6, "MHG.Relay"), + (7, "MHG.Buffer_WithOutput"), + (8, "MHG.Buffer"), + (9, "MHG.CircuitBoard"), + (10, "MHG.Mount"), + (11, "MHG.Peg"), + (12, "MHG.ThroughPeg"), + (13, "MHG.Socket"), + (14, "MHG.ThroughSocket"), + (15, "MHG.ChubbySocket"), + (16, "MHG.ChubbyThroughSocket"), + (17, "MHG.Label"), + (18, "MHG.PanelLabel"), + (19, "MHG.Chair"), + (20, "MHG.Flag"), + (21, "MHG.StandingDisplay"), + (22, "MHG.PanelDisplay"), + (23, "MHG.Singer"), + (24, "MHG.Drum"), + (25, "MHG.Switch"), + (26, "MHG.PanelSwitch"), + (27, "MHG.Button"), + (28, "MHG.PanelButton"), + (29, "MHG.Key"), + (30, "MHG.PanelKey"), +]; + +fn default_component_types_map() -> HashMap { + DEFAULT_COMPONENT_TYPES + .iter() + .map(|&(num, name)| (name.to_owned(), num)) + .collect() +}