Sandbox: Add savefile loading

This commit is contained in:
Adam Gausmann 2022-11-06 15:03:25 -06:00
parent 84a7cdccc2
commit d9f6311ae1
3 changed files with 177 additions and 20 deletions

View file

@ -26,17 +26,22 @@ pub struct Sandbox {
impl Sandbox { impl Sandbox {
/// Create a new, empty sandbox level. /// Create a new, empty sandbox level.
pub fn new() -> Self { 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<String, u16>, mods: Vec<ModInfo>) -> Self {
Self { Self {
next_component: 1, next_component: 1,
next_cluster: 0, next_cluster: 0,
next_wire: 0, next_wire: 0,
next_type: 0, next_type: component_types.values().copied().max().unwrap_or(0),
root_components: HashSet::new(), root_components: HashSet::new(),
components: HashMap::new(), components: HashMap::new(),
wires: HashMap::new(), wires: HashMap::new(),
clusters: HashMap::new(), clusters: HashMap::new(),
component_types: default_component_types_map(), component_types,
mods: Vec::new(), mods,
} }
} }
@ -61,46 +66,95 @@ impl Sandbox {
.collect(), .collect(),
custom_data: component.custom_data.clone(), custom_data: component.custom_data.clone(),
}; };
let id = ComponentId(NonZeroU32::new(self.next_component).unwrap()); let id = ComponentId(NonZeroU32::new(self.next_component).unwrap());
self.next_component += 1; self.insert_component(id, info);
id
}
self.components.insert(id, info); /// Internal logic of `add_component` that is shared with savefile loading.
if let Some(parent) = component.parent { /// 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 self.components
.get_mut(&parent) .get_mut(&parent)
.unwrap() .unwrap()
.children .children
.insert(id); .insert(id);
} else {
self.root_components.insert(id);
} }
id // Add component info.
self.components.insert(id, info);
} }
pub fn add_wire( pub fn add_wire(
&mut self, &mut self,
addr_a: PegAddress, addr_a: PegAddress,
addr_b: PegAddress, addr_b: PegAddress,
rotation: f32,
) -> Result<WireId, AddWireError> {
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<ClusterId>,
) -> Result<WireId, AddWireError> { ) -> Result<WireId, AddWireError> {
// It is illegal to directly connect output pegs. // It is illegal to directly connect output pegs.
if addr_a.peg_type == PegType::Output && addr_b.peg_type == PegType::Output { if addr_a.peg_type == PegType::Output && addr_b.peg_type == PegType::Output {
return Err(AddWireError::InvalidPegAddress)?; return Err(AddWireError::InvalidPegAddress)?;
} }
// If there is already a wire connecting these pegs, nothing needs to be
// done.
let peg_a = self let peg_a = self
.get_peg(&addr_a) .get_peg(&addr_a)
.ok_or(AddWireError::InvalidPegAddress)?; .ok_or(AddWireError::InvalidPegAddress)?;
let peg_b = self let peg_b = self
.get_peg(&addr_b) .get_peg(&addr_b)
.ok_or(AddWireError::InvalidPegAddress)?; .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() { if let Some(&wire_id) = peg_a.wires.intersection(&peg_b.wires).next() {
return Ok(wire_id); return Ok(wire_id);
} }
// Ensure both pegs have the same cluster. let cluster_id = match cluster_id {
let cluster_id = self.merge_clusters(peg_a.cluster_id, peg_b.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. // Create wire.
let wire_id = WireId(self.next_wire); let wire_id = WireId(self.next_wire);
@ -111,7 +165,7 @@ impl Sandbox {
a: addr_a, a: addr_a,
b: addr_b, b: addr_b,
cluster_id, 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); pub struct ComponentId(NonZeroU32);
impl ComponentId { impl ComponentId {
fn into_raw(self) -> u32 { fn into_raw(self) -> u32 {
self.0.into() self.0.into()
} }
fn from_raw(raw: u32) -> Option<Self> {
NonZeroU32::new(raw).map(Self)
}
} }
#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ClusterId(u32); pub struct ClusterId(u32);
impl ClusterId { impl ClusterId {
fn into_raw(self) -> i32 { fn into_raw(self) -> i32 {
self.0.try_into().unwrap() 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); pub struct WireId(u32);
#[derive(Debug)]
pub enum AddWireError { pub enum AddWireError {
InvalidPegAddress, InvalidPegAddress,
} }
#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PegType { pub enum PegType {
Input, Input,
Output, Output,
} }
#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PegAddress { pub struct PegAddress {
pub component: ComponentId, pub component: ComponentId,
pub peg_type: PegType, pub peg_type: PegType,

View file

@ -1,6 +1,7 @@
//! Conversions between the Sandbox editor and Blotter serialization types. //! Conversions between the Sandbox editor and Blotter serialization types.
use crate::latest as blotter; use crate::latest as blotter;
use std::collections::HashSet;
/// The current game version that this save is compatible with. /// The current game version that this save is compatible with.
const GAME_VERSION: [i32; 4] = [0, 91, 0, 510]; 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 { impl From<(super::ComponentId, &super::ComponentInfo)> for blotter::Component {
fn from((id, component): (super::ComponentId, &super::ComponentInfo)) -> Self { fn from((id, component): (super::ComponentId, &super::ComponentInfo)) -> Self {
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 { impl From<&super::PegInfo> for blotter::Input {
fn from(peg: &super::PegInfo) -> Self { fn from(peg: &super::PegInfo) -> Self {
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 { impl From<&super::PegInfo> for blotter::Output {
fn from(peg: &super::PegInfo) -> Self { fn from(peg: &super::PegInfo) -> Self {
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 { impl From<&super::WireInfo> for blotter::Wire {
fn from(wire: &super::WireInfo) -> Self { fn from(wire: &super::WireInfo) -> Self {
Self { Self {
@ -95,6 +169,17 @@ impl From<super::PegAddress> for blotter::PegAddress {
} }
} }
impl From<blotter::PegAddress> 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<super::PegType> for blotter::PegType { impl From<super::PegType> for blotter::PegType {
fn from(peg_type: super::PegType) -> Self { fn from(peg_type: super::PegType) -> Self {
match peg_type { match peg_type {
@ -103,3 +188,12 @@ impl From<super::PegType> for blotter::PegType {
} }
} }
} }
impl From<blotter::PegType> for super::PegType {
fn from(peg_type: blotter::PegType) -> Self {
match peg_type {
blotter::PegType::Input => Self::Input,
blotter::PegType::Output => Self::Output,
}
}
}

View file

@ -197,7 +197,7 @@ impl WriteTo for Component {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone, Copy)]
pub enum PegType { pub enum PegType {
Output, Output,
Input, Input,
@ -225,7 +225,7 @@ impl WriteTo for PegType {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone, Copy)]
pub struct PegAddress { pub struct PegAddress {
pub peg_type: PegType, pub peg_type: PegType,
pub component_address: u32, pub component_address: u32,