Compare commits
10 commits
a1966cfc70
...
ef9cce0d68
| Author | SHA1 | Date | |
|---|---|---|---|
| ef9cce0d68 | |||
| 6c2368f4cb | |||
| 4234140165 | |||
| da9d0fe09b | |||
| c6d7e57c59 | |||
| 935523a414 | |||
| 70f2a170b6 | |||
| 934055d060 | |||
| df7150ed83 | |||
| 5d063859d9 |
9 changed files with 847 additions and 239 deletions
|
|
@ -5,4 +5,5 @@ edition = "2021"
|
|||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
[dependencies]
|
||||
bitvec = "1.0"
|
||||
19
README.md
19
README.md
|
|
@ -1,7 +1,20 @@
|
|||
# Blotter.rs
|
||||
|
||||
A Rust parser for the Blotter file format.
|
||||
Blotter file format parser and Logic World sandbox editor in Rust.
|
||||
|
||||
Current version: [Blotter v5][spec]
|
||||
## Current version
|
||||
|
||||
[spec]: https://gist.github.com/JimmyCushnie/bebea37a21acbb6e669589967f9512a2
|
||||
This is the version that is supported for loading/saving and high-level editing:
|
||||
|
||||
- **Blotter v6** (Logic World 0.91 beta)
|
||||
- Not formalized yet, mostly same as v5, but component positions are
|
||||
integers and custom data formats changed for some components
|
||||
|
||||
## Legacy versions
|
||||
|
||||
There are no high-level APIs for these versions, but converting the savefile to
|
||||
newer versions is supported:
|
||||
|
||||
- [**Blotter v5**][v5] (Logic World 0.90)
|
||||
|
||||
[v5]: https://gist.github.com/JimmyCushnie/bebea37a21acbb6e669589967f9512a2
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ mod convert;
|
|||
pub mod custom_data;
|
||||
pub mod error;
|
||||
pub(crate) mod io;
|
||||
pub(crate) mod misc;
|
||||
pub mod sandbox;
|
||||
pub mod v5;
|
||||
pub mod v6;
|
||||
|
|
|
|||
104
src/misc/dense_store.rs
Normal file
104
src/misc/dense_store.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
use std::{
|
||||
fmt::Debug,
|
||||
hash::{Hash, Hasher},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
pub struct DenseStore<T> {
|
||||
items: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> DenseStore<T> {
|
||||
pub fn new() -> Self {
|
||||
Self { items: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.items.len()
|
||||
}
|
||||
|
||||
pub fn get(&self, index: Index<T>) -> Option<&T> {
|
||||
self.items.get(index.into_raw())
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, index: Index<T>) -> Option<&mut T> {
|
||||
self.items.get_mut(index.into_raw())
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, item: T) -> Index<T> {
|
||||
let index = Index::from_raw(self.items.len());
|
||||
self.items.push(item);
|
||||
index
|
||||
}
|
||||
|
||||
#[must_use = "DenseStore::remove() renames an index; all external references must be replaced"]
|
||||
pub fn remove(&mut self, index: Index<T>) -> Option<(T, Rename<T>)> {
|
||||
let raw = index.into_raw();
|
||||
if self.items.get(raw).is_some() {
|
||||
let removed = self.items.swap_remove(raw);
|
||||
Some((
|
||||
removed,
|
||||
Rename {
|
||||
src: Index::from_raw(self.items.len()),
|
||||
dest: index,
|
||||
},
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Index<T>(usize, PhantomData<T>);
|
||||
|
||||
impl<T> Index<T> {
|
||||
pub fn from_raw(raw: usize) -> Self {
|
||||
Self(raw, PhantomData)
|
||||
}
|
||||
|
||||
pub fn into_raw(self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for Index<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("Index").field(&self.0).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Index<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for Index<T> {}
|
||||
|
||||
impl<T> PartialEq for Index<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Eq for Index<T> {}
|
||||
|
||||
impl<T> Hash for Index<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Rename<T> {
|
||||
pub src: Index<T>,
|
||||
pub dest: Index<T>,
|
||||
}
|
||||
|
||||
impl<T> Debug for Rename<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Rename")
|
||||
.field("src", &self.src)
|
||||
.field("dest", &self.dest)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
4
src/misc/mod.rs
Normal file
4
src/misc/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
//! Miscellaneous internal utilities.
|
||||
|
||||
pub mod dense_store;
|
||||
pub mod object_store;
|
||||
161
src/misc/object_store.rs
Normal file
161
src/misc/object_store.rs
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
use std::{
|
||||
fmt::Debug,
|
||||
hash::{Hash, Hasher},
|
||||
marker::PhantomData,
|
||||
mem::replace,
|
||||
};
|
||||
|
||||
/// An object store that stores a free-list inline with its elements.
|
||||
///
|
||||
/// The original idea for this allocator is to have a stack of indexes that have
|
||||
/// been vacated when items are removed from those locations. When adding an
|
||||
/// item, the stack is checked first for a free index; if the stack is empty,
|
||||
/// then the list has no vacant entries and the item is simply appended to the
|
||||
/// end.
|
||||
///
|
||||
/// Instead of storing this stack separately; the cost of the free-list stack is
|
||||
/// amortized by storing it inline with the items themselves. This may require
|
||||
/// up to 8 bytes in addition to the size of each item.
|
||||
pub struct ObjectStore<T> {
|
||||
first_vacant: usize,
|
||||
entries: Vec<Entry<T>>,
|
||||
}
|
||||
|
||||
impl<T> ObjectStore<T> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
first_vacant: usize::MAX,
|
||||
entries: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn contains(&self, address: Address<T>) -> bool {
|
||||
// self.get(address).is_some()
|
||||
// }
|
||||
|
||||
pub fn insert(&mut self, item: T) -> Address<T> {
|
||||
if let Some(entry) = self.entries.get_mut(self.first_vacant) {
|
||||
let address = Address::from_raw(self.first_vacant);
|
||||
let replaced = replace(entry, Entry::Occupied(item));
|
||||
match replaced {
|
||||
Entry::Vacant { next_vacant } => {
|
||||
self.first_vacant = next_vacant;
|
||||
}
|
||||
_ => {
|
||||
unreachable!("occupied entry in free list");
|
||||
}
|
||||
}
|
||||
address
|
||||
} else {
|
||||
let address = Address::from_raw(self.entries.len());
|
||||
self.entries.push(Entry::Occupied(item));
|
||||
address
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, address: Address<T>) -> Option<&T> {
|
||||
self.entries.get(address.into_raw()).and_then(Entry::get)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, address: Address<T>) -> Option<&mut T> {
|
||||
self.entries
|
||||
.get_mut(address.into_raw())
|
||||
.and_then(Entry::get_mut)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, address: Address<T>) -> Option<T> {
|
||||
let index = address.into_raw();
|
||||
|
||||
match self.entries.get(index)? {
|
||||
Entry::Occupied(_) => {
|
||||
let replaced = replace(
|
||||
&mut self.entries[index],
|
||||
Entry::Vacant {
|
||||
next_vacant: self.first_vacant,
|
||||
},
|
||||
);
|
||||
self.first_vacant = index;
|
||||
match replaced {
|
||||
Entry::Occupied(x) => Some(x),
|
||||
_ => unreachable!("occupied is not occupied?"),
|
||||
}
|
||||
}
|
||||
Entry::Vacant { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (Address<T>, &T)> {
|
||||
self.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(index, entry)| entry.get().map(|item| (Address::from_raw(index), item)))
|
||||
}
|
||||
|
||||
// pub fn iter_mut(&mut self) -> impl Iterator<Item = (Address<T>, &mut T)> {
|
||||
// self.entries
|
||||
// .iter_mut()
|
||||
// .enumerate()
|
||||
// .flat_map(|(index, entry)| entry.get_mut().map(|item| (Address::from_raw(index), item)))
|
||||
// }
|
||||
}
|
||||
|
||||
enum Entry<T> {
|
||||
Vacant { next_vacant: usize },
|
||||
Occupied(T),
|
||||
}
|
||||
|
||||
impl<T> Entry<T> {
|
||||
fn get(&self) -> Option<&T> {
|
||||
match self {
|
||||
Self::Occupied(x) => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self) -> Option<&mut T> {
|
||||
match self {
|
||||
Self::Occupied(x) => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Address<T>(usize, PhantomData<T>);
|
||||
|
||||
impl<T> Address<T> {
|
||||
pub fn from_raw(raw: usize) -> Self {
|
||||
Self(raw, PhantomData)
|
||||
}
|
||||
|
||||
pub fn into_raw(self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for Address<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("Address").field(&self.0).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Address<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for Address<T> {}
|
||||
|
||||
impl<T> PartialEq for Address<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Eq for Address<T> {}
|
||||
|
||||
impl<T> Hash for Address<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.hash(state);
|
||||
}
|
||||
}
|
||||
125
src/sandbox/component.rs
Normal file
125
src/sandbox/component.rs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
use super::ComponentBuilder;
|
||||
|
||||
pub struct CircuitBoard {
|
||||
color: [u8; 3],
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl CircuitBoard {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
color: [0x78, 0x78, 0x78],
|
||||
width: 1,
|
||||
height: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn width(self, width: u32) -> Self {
|
||||
assert!(width > 0);
|
||||
Self { width, ..self }
|
||||
}
|
||||
|
||||
pub fn height(self, height: u32) -> Self {
|
||||
assert!(height > 0);
|
||||
Self { height, ..self }
|
||||
}
|
||||
|
||||
pub fn color(self, color: [u8; 3]) -> Self {
|
||||
Self { color, ..self }
|
||||
}
|
||||
|
||||
pub fn build(self) -> ComponentBuilder<'static> {
|
||||
ComponentBuilder::new("MHG.CircuitBoard").custom_data(self.custom_data())
|
||||
}
|
||||
|
||||
fn custom_data(&self) -> Option<Vec<u8>> {
|
||||
let mut bytes = Vec::with_capacity(11);
|
||||
bytes.extend(self.color);
|
||||
bytes.extend(self.width.to_le_bytes());
|
||||
bytes.extend(self.height.to_le_bytes());
|
||||
Some(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CircuitBoard> for ComponentBuilder<'static> {
|
||||
fn from(board: CircuitBoard) -> Self {
|
||||
board.build()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Delayer {
|
||||
delay: u32,
|
||||
timer: u32,
|
||||
}
|
||||
|
||||
impl Delayer {
|
||||
pub fn new() -> Self {
|
||||
Self { delay: 2, timer: 0 }
|
||||
}
|
||||
|
||||
pub fn delay(self, delay: u32) -> Self {
|
||||
Self { delay, ..self }
|
||||
}
|
||||
|
||||
pub fn timer(self, timer: u32) -> Self {
|
||||
Self { timer, ..self }
|
||||
}
|
||||
|
||||
pub fn build(self) -> ComponentBuilder<'static> {
|
||||
let mut custom_data = Vec::with_capacity(8);
|
||||
custom_data.extend(self.timer.to_le_bytes());
|
||||
custom_data.extend(self.delay.to_le_bytes());
|
||||
|
||||
ComponentBuilder::new("MHG.Delayer")
|
||||
.num_inputs(1)
|
||||
.num_outputs(1)
|
||||
.custom_data(Some(custom_data))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Delayer> for ComponentBuilder<'static> {
|
||||
fn from(delayer: Delayer) -> Self {
|
||||
delayer.build()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChubbySocket {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl ChubbySocket {
|
||||
pub fn new() -> Self {
|
||||
Self { _private: () }
|
||||
}
|
||||
|
||||
pub fn build(self) -> ComponentBuilder<'static> {
|
||||
ComponentBuilder::new("MHG.ChubbySocket").num_inputs(1)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChubbySocket> for ComponentBuilder<'static> {
|
||||
fn from(chubby_socket: ChubbySocket) -> Self {
|
||||
chubby_socket.build()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Peg {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl Peg {
|
||||
pub fn new() -> Self {
|
||||
Self { _private: () }
|
||||
}
|
||||
|
||||
pub fn build(self) -> ComponentBuilder<'static> {
|
||||
ComponentBuilder::new("MHG.Peg").num_inputs(1)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Peg> for ComponentBuilder<'static> {
|
||||
fn from(peg: Peg) -> Self {
|
||||
peg.build()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,33 @@
|
|||
//! High-level API for constructing and modifying a "Sandbox" / World.
|
||||
|
||||
pub mod component;
|
||||
mod serialize;
|
||||
|
||||
use crate::latest::ModInfo;
|
||||
use bitvec::vec::BitVec;
|
||||
|
||||
use crate::{
|
||||
latest::ModInfo,
|
||||
misc::{
|
||||
dense_store::{DenseStore, Index},
|
||||
object_store::{Address, ObjectStore},
|
||||
},
|
||||
};
|
||||
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<ComponentId>,
|
||||
components: HashMap<ComponentId, ComponentInfo>,
|
||||
wires: HashMap<WireId, WireInfo>,
|
||||
clusters: HashMap<ClusterId, ClusterInfo>,
|
||||
components: ObjectStore<ComponentInfo>,
|
||||
wires: ObjectStore<WireInfo>,
|
||||
nets: DenseStore<NetInfo>,
|
||||
net_states: BitVec<u8>,
|
||||
|
||||
next_type: u16,
|
||||
component_types: HashMap<String, u16>,
|
||||
|
||||
mods: Vec<ModInfo>,
|
||||
}
|
||||
|
||||
|
|
@ -32,15 +40,15 @@ impl Sandbox {
|
|||
/// 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 {
|
||||
next_component: 1,
|
||||
next_cluster: 0,
|
||||
next_wire: 0,
|
||||
next_type: component_types.values().copied().max().unwrap_or(0),
|
||||
root_components: HashSet::new(),
|
||||
components: HashMap::new(),
|
||||
wires: HashMap::new(),
|
||||
clusters: HashMap::new(),
|
||||
components: ObjectStore::new(),
|
||||
wires: ObjectStore::new(),
|
||||
nets: DenseStore::new(),
|
||||
net_states: BitVec::new(),
|
||||
|
||||
next_type: component_types.values().copied().max().unwrap_or(0),
|
||||
component_types,
|
||||
|
||||
mods,
|
||||
}
|
||||
}
|
||||
|
|
@ -53,35 +61,52 @@ impl Sandbox {
|
|||
rotation: component.rotation,
|
||||
children: HashSet::new(),
|
||||
inputs: repeat_with(|| PegInfo {
|
||||
cluster_id: self.make_cluster(),
|
||||
net_id: self.make_net(),
|
||||
wires: HashSet::new(),
|
||||
})
|
||||
.take(component.num_inputs as usize)
|
||||
.collect(),
|
||||
outputs: repeat_with(|| PegInfo {
|
||||
cluster_id: self.make_cluster(),
|
||||
net_id: self.make_net(),
|
||||
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.insert_component(id, info);
|
||||
id
|
||||
self.insert_component(info)
|
||||
}
|
||||
|
||||
/// 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));
|
||||
// Manually insert a generated component info.
|
||||
// Used to efficiently insert data from a save file.
|
||||
fn insert_component(&mut self, info: ComponentInfo) -> ComponentId {
|
||||
// Add component info.
|
||||
let id = ComponentId(self.components.insert(info));
|
||||
let info = self.components.get(id.0).unwrap();
|
||||
|
||||
// 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 peg-net cross-references.
|
||||
for (peg_index, peg_info) in info.inputs.iter().enumerate() {
|
||||
self.nets
|
||||
.get_mut(peg_info.net_id.0)
|
||||
.unwrap()
|
||||
.pegs
|
||||
.insert(PegAddress {
|
||||
component: id,
|
||||
peg_type: PegType::Input,
|
||||
peg_index,
|
||||
});
|
||||
}
|
||||
for (peg_index, peg_info) in info.outputs.iter().enumerate() {
|
||||
self.nets
|
||||
.get_mut(peg_info.net_id.0)
|
||||
.unwrap()
|
||||
.pegs
|
||||
.insert(PegAddress {
|
||||
component: id,
|
||||
peg_type: PegType::Output,
|
||||
peg_index,
|
||||
});
|
||||
}
|
||||
|
||||
// Add parent-child cross-reference.
|
||||
if let Some(parent) = info.parent {
|
||||
|
|
@ -89,7 +114,7 @@ impl Sandbox {
|
|||
// 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)
|
||||
.get_mut(parent.0)
|
||||
.unwrap()
|
||||
.children
|
||||
.insert(id);
|
||||
|
|
@ -97,8 +122,7 @@ impl Sandbox {
|
|||
self.root_components.insert(id);
|
||||
}
|
||||
|
||||
// Add component info.
|
||||
self.components.insert(id, info);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn add_wire(
|
||||
|
|
@ -119,7 +143,7 @@ impl Sandbox {
|
|||
addr_a: PegAddress,
|
||||
addr_b: PegAddress,
|
||||
rotation: f32,
|
||||
cluster_id: Option<ClusterId>,
|
||||
net_id: Option<NetId>,
|
||||
) -> Result<WireId, AddWireError> {
|
||||
// It is illegal to directly connect output pegs.
|
||||
if addr_a.peg_type == PegType::Output && addr_b.peg_type == PegType::Output {
|
||||
|
|
@ -138,62 +162,125 @@ impl Sandbox {
|
|||
return Ok(wire_id);
|
||||
}
|
||||
|
||||
let cluster_id = match cluster_id {
|
||||
let net_id = match net_id {
|
||||
Some(id) => {
|
||||
// If cluster ID is specified (i.e. from an existing savefile),
|
||||
// If net ID is specified (i.e. from an existing savefile),
|
||||
// check savefile validity: ensure that both endpoints and the
|
||||
// wire all have the same cluster.
|
||||
// wire all have the same net.
|
||||
|
||||
//TODO bubble this error
|
||||
assert!(id == peg_a.cluster_id && id == peg_b.cluster_id);
|
||||
assert!(
|
||||
(addr_a.peg_type == PegType::Output || id == peg_b.net_id)
|
||||
&& (addr_b.peg_type == PegType::Output || id == peg_a.net_id),
|
||||
"{:?}\n{:?} {:?}\n{:?} {:?}",
|
||||
id,
|
||||
peg_a.net_id,
|
||||
addr_a,
|
||||
peg_b.net_id,
|
||||
addr_b
|
||||
);
|
||||
|
||||
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)
|
||||
// If no net ID is given (i.e. caller is `add_wire`), then:
|
||||
|
||||
// If one of the pegs is an output, use that peg's ID.
|
||||
if addr_a.peg_type == PegType::Output {
|
||||
peg_a.net_id
|
||||
} else if addr_b.peg_type == PegType::Output {
|
||||
peg_b.net_id
|
||||
} else {
|
||||
// If both pegs are inputs, obtain one net from merging the two.
|
||||
self.merge_nets(peg_a.net_id, peg_b.net_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,
|
||||
},
|
||||
);
|
||||
let info = WireInfo {
|
||||
a: addr_a,
|
||||
b: addr_b,
|
||||
net_id,
|
||||
rotation,
|
||||
};
|
||||
let wire_id = WireId(self.wires.insert(info));
|
||||
|
||||
// Register wire references in pegs and cluster.
|
||||
// Register wire references in pegs and net.
|
||||
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);
|
||||
self.nets.get_mut(net_id.0).unwrap().wires.insert(wire_id);
|
||||
|
||||
Ok(wire_id)
|
||||
}
|
||||
|
||||
pub fn remove_component(&mut self, id: ComponentId) {
|
||||
// TODO Remove all wires connected to this component.
|
||||
// Remove component.
|
||||
let component = match self.components.remove(id.0) {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
// If component doesn't exist, nothing needs to be done.
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO Remove component.
|
||||
// For each peg in the removed component:
|
||||
let inputs = component.inputs.iter().enumerate().map(|(index, peg)| {
|
||||
(
|
||||
PegAddress {
|
||||
component: id,
|
||||
peg_type: PegType::Input,
|
||||
peg_index: index,
|
||||
},
|
||||
peg,
|
||||
)
|
||||
});
|
||||
let outputs = component.outputs.iter().enumerate().map(|(index, peg)| {
|
||||
(
|
||||
PegAddress {
|
||||
component: id,
|
||||
peg_type: PegType::Output,
|
||||
peg_index: index,
|
||||
},
|
||||
peg,
|
||||
)
|
||||
});
|
||||
for (peg_addr, peg) in inputs.chain(outputs) {
|
||||
// Remove all wires connected to this peg.
|
||||
for wire_id in &peg.wires {
|
||||
// It is safe to call this here; it is designed to handle the
|
||||
// case where one or more pegs do not exist.
|
||||
self.remove_wire(*wire_id);
|
||||
}
|
||||
|
||||
// TODO Remove peg-cluster cross-references. Remove clusters if empty.
|
||||
// Remove peg-net cross-references. Remove nets if empty.
|
||||
let net = self.nets.get_mut(peg.net_id.0).unwrap();
|
||||
net.pegs.remove(&peg_addr);
|
||||
if net.size() == 0 {
|
||||
self.remove_net(peg.net_id);
|
||||
}
|
||||
}
|
||||
|
||||
todo!()
|
||||
// Remove component-parent cross-references.
|
||||
// If the parent does not exist, we may be a child of a just-deleted
|
||||
// parent; ignore it.
|
||||
if let Some(parent) = component
|
||||
.parent
|
||||
.and_then(|parent_id| self.components.get_mut(parent_id.0))
|
||||
{
|
||||
parent.children.remove(&id);
|
||||
}
|
||||
|
||||
// If the component has any children, remove them.
|
||||
// TODO refactor this recursion?
|
||||
for child in component.children {
|
||||
self.remove_component(child)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_wire(&mut self, id: WireId) {
|
||||
// Remove wire.
|
||||
let wire = match self.wires.remove(&id) {
|
||||
let wire = match self.wires.remove(id.0) {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
// If wire doesn't exist, nothing needs to be done.
|
||||
|
|
@ -201,16 +288,18 @@ impl Sandbox {
|
|||
}
|
||||
};
|
||||
|
||||
// Remove wire-cluster and wire-peg cross-references.
|
||||
self.clusters
|
||||
.get_mut(&wire.cluster_id)
|
||||
.unwrap()
|
||||
.wires
|
||||
.remove(&id);
|
||||
self.get_peg_mut(&wire.a).unwrap().wires.remove(&id);
|
||||
self.get_peg_mut(&wire.b).unwrap().wires.remove(&id);
|
||||
// Remove wire-net and wire-peg cross-references.
|
||||
self.nets.get_mut(wire.net_id.0).unwrap().wires.remove(&id);
|
||||
// The pegs might not exist if a component was just removed.
|
||||
// That is expected and should not panic/error:
|
||||
if let Some(peg) = self.get_peg_mut(&wire.a) {
|
||||
peg.wires.remove(&id);
|
||||
}
|
||||
if let Some(peg) = self.get_peg_mut(&wire.b) {
|
||||
peg.wires.remove(&id);
|
||||
}
|
||||
|
||||
// Split cluster if necessary.
|
||||
// Split net if necessary.
|
||||
self.check_split(&wire.a, &wire.b);
|
||||
}
|
||||
|
||||
|
|
@ -226,38 +315,64 @@ impl Sandbox {
|
|||
}
|
||||
}
|
||||
|
||||
fn make_cluster(&mut self) -> ClusterId {
|
||||
let id = ClusterId(self.next_cluster);
|
||||
self.next_cluster += 1;
|
||||
id
|
||||
fn make_net(&mut self) -> NetId {
|
||||
assert_eq!(self.nets.len(), self.net_states.len());
|
||||
|
||||
self.net_states.push(false);
|
||||
NetId(self.nets.insert(NetInfo {
|
||||
wires: HashSet::new(),
|
||||
pegs: HashSet::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn merge_clusters(&mut self, id_a: ClusterId, id_b: ClusterId) -> ClusterId {
|
||||
// Nothing needs to be done if the two clusters are the same.
|
||||
fn remove_net(&mut self, net_id: NetId) -> Option<NetInfo> {
|
||||
if let Some((net, rename)) = self.nets.remove(net_id.0) {
|
||||
// Rename all the references to the net that was moved into this position.
|
||||
let renamed = self.nets.get(rename.dest).unwrap();
|
||||
for wire_id in &renamed.wires {
|
||||
self.wires.get_mut(wire_id.0).unwrap().net_id = NetId(rename.dest);
|
||||
}
|
||||
//TODO remove this clone, reduce size of get_peg_mut borrow
|
||||
for peg_id in &renamed.pegs.clone() {
|
||||
self.get_peg_mut(peg_id).unwrap().net_id = NetId(rename.dest);
|
||||
}
|
||||
|
||||
// Perform the same swap-remove in net_states:
|
||||
self.net_states.swap_remove(rename.dest.into_raw());
|
||||
|
||||
assert_eq!(self.nets.len(), self.net_states.len());
|
||||
Some(net)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_nets(&mut self, id_a: NetId, id_b: NetId) -> NetId {
|
||||
// Nothing needs to be done if the two nets 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 a = self.nets.get(id_a.0).unwrap();
|
||||
let b = &self.nets.get(id_b.0).unwrap();
|
||||
// Merge the smaller net into the larger net.
|
||||
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:
|
||||
let src = self.remove_net(id_src).unwrap();
|
||||
// Update net cross-references:
|
||||
for wire_id in &src.wires {
|
||||
self.wires.get_mut(wire_id).unwrap().cluster_id = id_dest;
|
||||
self.wires.get_mut(wire_id.0).unwrap().net_id = id_dest;
|
||||
}
|
||||
for peg_id in &src.pegs {
|
||||
self.get_peg_mut(peg_id).unwrap().cluster_id = id_dest;
|
||||
self.get_peg_mut(peg_id).unwrap().net_id = id_dest;
|
||||
}
|
||||
|
||||
// Move references from src into dest:
|
||||
let dest = self.clusters.get_mut(&id_dest).unwrap();
|
||||
let dest = self.nets.get_mut(id_dest.0).unwrap();
|
||||
dest.pegs.extend(src.pegs);
|
||||
dest.wires.extend(src.wires);
|
||||
|
||||
|
|
@ -265,9 +380,31 @@ impl Sandbox {
|
|||
}
|
||||
|
||||
fn check_split(&mut self, addr_a: &PegAddress, addr_b: &PegAddress) {
|
||||
// Traverse the whole cluster connected to addr_a.
|
||||
match (self.get_peg(addr_a), self.get_peg(addr_b)) {
|
||||
(None, _) | (_, None) => {
|
||||
// At least one of the pegs does not exist - nothing needs to be
|
||||
// split.
|
||||
//
|
||||
// This can happen while a component is being removed; its pegs
|
||||
// are also being removed and any wires connected to them, and
|
||||
// so they will not need a new net.
|
||||
return;
|
||||
}
|
||||
(Some(peg_a), Some(peg_b)) if peg_a.net_id != peg_b.net_id => {
|
||||
// The pegs do not have the same net ID. This happens when one
|
||||
// of them is an output peg (however, they cannot _both_ be output pegs.
|
||||
assert!(addr_a.peg_type != addr_b.peg_type);
|
||||
// In this case, nothing needs to be split.
|
||||
return;
|
||||
}
|
||||
_ => {
|
||||
// In any other case, we need to split.
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse the whole net connected to addr_a.
|
||||
// If addr_b is in that set, then they are connected.
|
||||
// If not, a new cluster needs to be created.
|
||||
// If not, a new net needs to be created.
|
||||
|
||||
let mut frontier = Vec::new();
|
||||
let mut visited_pegs = HashSet::new();
|
||||
|
|
@ -277,7 +414,7 @@ impl Sandbox {
|
|||
|
||||
while let Some(peg_addr) = frontier.pop() {
|
||||
for wire_id in &self.get_peg(&peg_addr).unwrap().wires {
|
||||
let wire = &self.wires[wire_id];
|
||||
let wire = self.wires.get(wire_id.0).unwrap();
|
||||
let neighbor = if peg_addr == wire.a {
|
||||
wire.b
|
||||
} else if peg_addr == wire.b {
|
||||
|
|
@ -297,26 +434,26 @@ impl Sandbox {
|
|||
return;
|
||||
}
|
||||
|
||||
// B is not connected to A; make a new cluster and update all the
|
||||
// B is not connected to A; make a new net and update all the
|
||||
// pegs and wires found connected to A.
|
||||
let cluster_id = self.make_cluster();
|
||||
let net_id = self.make_net();
|
||||
for peg_addr in visited_pegs {
|
||||
self.get_peg_mut(&peg_addr).unwrap().cluster_id = cluster_id;
|
||||
self.get_peg_mut(&peg_addr).unwrap().net_id = net_id;
|
||||
}
|
||||
for wire_id in visited_wires {
|
||||
self.wires.get_mut(&wire_id).unwrap().cluster_id = cluster_id;
|
||||
self.wires.get_mut(wire_id.0).unwrap().net_id = net_id;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_peg(&self, addr: &PegAddress) -> Option<&PegInfo> {
|
||||
self.components
|
||||
.get(&addr.component)
|
||||
.get(addr.component.0)
|
||||
.and_then(|component| component.get_peg(addr))
|
||||
}
|
||||
|
||||
fn get_peg_mut(&mut self, addr: &PegAddress) -> Option<&mut PegInfo> {
|
||||
self.components
|
||||
.get_mut(&addr.component)
|
||||
.get_mut(addr.component.0)
|
||||
.and_then(|component| component.get_peg_mut(addr))
|
||||
}
|
||||
}
|
||||
|
|
@ -377,33 +514,23 @@ impl<'a> ComponentBuilder<'a> {
|
|||
}
|
||||
|
||||
#[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<Self> {
|
||||
NonZeroU32::new(raw).map(Self)
|
||||
}
|
||||
}
|
||||
pub struct ComponentId(Address<ComponentInfo>);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ClusterId(u32);
|
||||
pub struct NetId(Index<NetInfo>);
|
||||
|
||||
impl ClusterId {
|
||||
impl NetId {
|
||||
fn into_raw(self) -> i32 {
|
||||
self.0.try_into().unwrap()
|
||||
self.0.into_raw().try_into().unwrap()
|
||||
}
|
||||
|
||||
fn from_raw(raw: i32) -> Self {
|
||||
Self(raw.try_into().unwrap())
|
||||
Self(Index::from_raw(raw.try_into().unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct WireId(u32);
|
||||
pub struct WireId(Address<WireInfo>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AddWireError {
|
||||
|
|
@ -451,7 +578,7 @@ impl ComponentInfo {
|
|||
}
|
||||
|
||||
struct PegInfo {
|
||||
cluster_id: ClusterId,
|
||||
net_id: NetId,
|
||||
wires: HashSet<WireId>,
|
||||
}
|
||||
|
||||
|
|
@ -459,16 +586,16 @@ struct PegInfo {
|
|||
struct WireInfo {
|
||||
a: PegAddress,
|
||||
b: PegAddress,
|
||||
cluster_id: ClusterId,
|
||||
net_id: NetId,
|
||||
rotation: f32,
|
||||
}
|
||||
|
||||
struct ClusterInfo {
|
||||
struct NetInfo {
|
||||
wires: HashSet<WireId>,
|
||||
pegs: HashSet<PegAddress>,
|
||||
}
|
||||
|
||||
impl ClusterInfo {
|
||||
impl NetInfo {
|
||||
fn size(&self) -> usize {
|
||||
self.wires.len() + self.pegs.len()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,111 @@
|
|||
//! Conversions between the Sandbox editor and Blotter serialization types.
|
||||
|
||||
use bitvec::vec::BitVec;
|
||||
|
||||
use crate::latest as blotter;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
/// The current game version that this save is compatible with.
|
||||
const GAME_VERSION: [i32; 4] = [0, 91, 0, 510];
|
||||
|
||||
/// Serialization context, mainly tracks ID mappings
|
||||
struct Serializer {
|
||||
next_component_id: u32,
|
||||
component_id_map: HashMap<super::ComponentId, u32>,
|
||||
}
|
||||
|
||||
impl Serializer {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
next_component_id: 1,
|
||||
component_id_map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn register_component(&mut self, id: super::ComponentId) -> u32 {
|
||||
let raw_id = self.next_component_id;
|
||||
self.next_component_id += 1;
|
||||
self.component_id_map.insert(id, raw_id);
|
||||
raw_id
|
||||
}
|
||||
|
||||
fn get_component(&self, id: super::ComponentId) -> u32 {
|
||||
self.component_id_map[&id]
|
||||
}
|
||||
|
||||
fn serialize_component(
|
||||
&mut self,
|
||||
id: super::ComponentId,
|
||||
info: &super::ComponentInfo,
|
||||
) -> blotter::Component {
|
||||
blotter::Component {
|
||||
address: self.register_component(id),
|
||||
parent: info.parent.map(|id| self.get_component(id)).unwrap_or(0),
|
||||
type_id: info.type_id,
|
||||
position: info.position,
|
||||
rotation: info.rotation,
|
||||
inputs: info
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|peg| self.serialize_input(peg))
|
||||
.collect(),
|
||||
outputs: info
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|peg| self.serialize_output(peg))
|
||||
.collect(),
|
||||
custom_data: info.custom_data.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_input(&self, peg: &super::PegInfo) -> blotter::Input {
|
||||
blotter::Input {
|
||||
circuit_state_id: peg.net_id.into_raw(),
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_output(&self, peg: &super::PegInfo) -> blotter::Output {
|
||||
blotter::Output {
|
||||
circuit_state_id: peg.net_id.into_raw(),
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_wire(&self, wire: &super::WireInfo) -> blotter::Wire {
|
||||
blotter::Wire {
|
||||
start_peg: self.serialize_peg_address(&wire.a),
|
||||
end_peg: self.serialize_peg_address(&wire.b),
|
||||
circuit_state_id: wire.net_id.into_raw(),
|
||||
rotation: wire.rotation,
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_peg_address(&self, addr: &super::PegAddress) -> blotter::PegAddress {
|
||||
blotter::PegAddress {
|
||||
peg_type: addr.peg_type.into(),
|
||||
component_address: self.get_component(addr.component),
|
||||
peg_index: addr.peg_index.try_into().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&super::Sandbox> for blotter::BlotterFile {
|
||||
fn from(sandbox: &super::Sandbox) -> Self {
|
||||
let mut ser = Serializer::new();
|
||||
|
||||
// Blotter format requires that parents must be serialized before children.
|
||||
// TODO Serialize components with depth-first, pre-order traversal.
|
||||
// Serialize components with depth-first, pre-order traversal.
|
||||
let mut components = Vec::new();
|
||||
let mut stack = Vec::new();
|
||||
let mut stack: Vec<super::ComponentId> = Vec::new();
|
||||
stack.extend(&sandbox.root_components);
|
||||
while let Some(component_id) = stack.pop() {
|
||||
let component = &sandbox.components[&component_id];
|
||||
let component = sandbox.components.get(component_id.0).unwrap();
|
||||
stack.extend(&component.children);
|
||||
components.push((component_id, component).into())
|
||||
components.push(ser.serialize_component(component_id, component))
|
||||
}
|
||||
|
||||
let mut states = sandbox.net_states.clone();
|
||||
states.set_uninitialized(false);
|
||||
|
||||
Self {
|
||||
game_version: GAME_VERSION,
|
||||
save_type: blotter::SaveType::World,
|
||||
|
|
@ -35,17 +122,96 @@ impl From<&super::Sandbox> for blotter::BlotterFile {
|
|||
wires: sandbox
|
||||
.wires
|
||||
.iter()
|
||||
.map(|(_id, wire)| wire.into())
|
||||
.map(|(_id, wire)| ser.serialize_wire(wire))
|
||||
.collect(),
|
||||
circuit_states: blotter::CircuitStates::WorldFormat {
|
||||
circuit_states: vec![], //TODO
|
||||
circuit_states: states.into_vec(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialization context, mainly tracks ID mappings
|
||||
struct Deserializer {
|
||||
component_id_map: HashMap<u32, super::ComponentId>,
|
||||
}
|
||||
|
||||
impl Deserializer {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
component_id_map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn register_component(&mut self, raw_id: u32, id: super::ComponentId) {
|
||||
self.component_id_map.insert(raw_id, id);
|
||||
}
|
||||
|
||||
fn get_component(&self, id: u32) -> super::ComponentId {
|
||||
self.component_id_map[&id]
|
||||
}
|
||||
|
||||
fn deserialize_component(&self, component: &blotter::Component) -> super::ComponentInfo {
|
||||
super::ComponentInfo {
|
||||
type_id: component.type_id,
|
||||
parent: if component.parent == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(self.get_component(component.parent))
|
||||
},
|
||||
position: component.position,
|
||||
rotation: component.rotation,
|
||||
children: HashSet::new(),
|
||||
inputs: component
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|input| self.deserialize_input(input))
|
||||
.collect(),
|
||||
outputs: component
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|output| self.deserialize_output(output))
|
||||
.collect(),
|
||||
custom_data: component.custom_data.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_input(&self, input: &blotter::Input) -> super::PegInfo {
|
||||
super::PegInfo {
|
||||
net_id: super::NetId::from_raw(input.circuit_state_id),
|
||||
wires: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_output(&self, output: &blotter::Output) -> super::PegInfo {
|
||||
super::PegInfo {
|
||||
net_id: super::NetId::from_raw(output.circuit_state_id),
|
||||
wires: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_wire(&self, wire: &blotter::Wire) -> super::WireInfo {
|
||||
super::WireInfo {
|
||||
a: self.deserialize_peg_address(&wire.start_peg),
|
||||
b: self.deserialize_peg_address(&wire.end_peg),
|
||||
net_id: super::NetId::from_raw(wire.circuit_state_id),
|
||||
rotation: wire.rotation,
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_peg_address(&self, addr: &blotter::PegAddress) -> super::PegAddress {
|
||||
super::PegAddress {
|
||||
peg_type: addr.peg_type.into(),
|
||||
component: self.get_component(addr.component_address),
|
||||
peg_index: addr.peg_index.try_into().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&blotter::BlotterFile> for super::Sandbox {
|
||||
fn from(file: &blotter::BlotterFile) -> Self {
|
||||
let mut de = Deserializer::new();
|
||||
|
||||
// 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.
|
||||
|
|
@ -56,130 +222,36 @@ impl From<&blotter::BlotterFile> for super::Sandbox {
|
|||
.collect(),
|
||||
file.mods.clone(),
|
||||
);
|
||||
|
||||
match &file.circuit_states {
|
||||
blotter::CircuitStates::WorldFormat { circuit_states } => {
|
||||
sandbox.net_states = BitVec::from_slice(circuit_states);
|
||||
for _ in 0..8 * circuit_states.len() {
|
||||
sandbox.nets.insert(super::NetInfo {
|
||||
wires: HashSet::new(),
|
||||
pegs: HashSet::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => panic!("unsupported circuit state format"),
|
||||
}
|
||||
|
||||
for component in &file.components {
|
||||
let (id, info) = component.into();
|
||||
sandbox.insert_component(id, info);
|
||||
let id = sandbox.insert_component(de.deserialize_component(component));
|
||||
de.register_component(component.address, id);
|
||||
}
|
||||
for wire in &file.wires {
|
||||
// TODO bubble error
|
||||
let info = de.deserialize_wire(wire);
|
||||
sandbox
|
||||
.insert_wire(
|
||||
wire.start_peg.into(),
|
||||
wire.end_peg.into(),
|
||||
wire.rotation,
|
||||
Some(super::ClusterId::from_raw(wire.circuit_state_id)),
|
||||
)
|
||||
.insert_wire(info.a, info.b, info.rotation, Some(info.net_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 {
|
||||
address: id.into_raw(),
|
||||
parent: component.parent.map(|id| id.into_raw()).unwrap_or(0),
|
||||
type_id: component.type_id,
|
||||
position: component.position,
|
||||
rotation: component.rotation,
|
||||
inputs: component.inputs.iter().map(|peg| peg.into()).collect(),
|
||||
outputs: component.outputs.iter().map(|peg| peg.into()).collect(),
|
||||
custom_data: component.custom_data.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
circuit_state_id: peg.cluster_id.into_raw(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
circuit_state_id: peg.cluster_id.into_raw(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
start_peg: wire.a.into(),
|
||||
end_peg: wire.b.into(),
|
||||
circuit_state_id: wire.cluster_id.into_raw(),
|
||||
rotation: wire.rotation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<super::PegAddress> for blotter::PegAddress {
|
||||
fn from(addr: super::PegAddress) -> Self {
|
||||
Self {
|
||||
peg_type: addr.peg_type.into(),
|
||||
component_address: addr.component.into_raw(),
|
||||
peg_index: addr.peg_index.try_into().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
fn from(peg_type: super::PegType) -> Self {
|
||||
match peg_type {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue