Compare commits

...

10 commits

Author SHA1 Message Date
ef9cce0d68 Sandbox: Add component builders for some common components 2022-11-11 20:08:43 -06:00
6c2368f4cb impl Debug for dense_store::Rename 2022-11-11 20:08:21 -06:00
4234140165 Sandbox: Fix incorrect assumption that wire net ignores output pegs
In fact, if a wire is connected to an output peg,
it has the same net state as the output peg.
The net then changes between the wire and the input peg that it connects to.
2022-11-11 20:07:48 -06:00
da9d0fe09b Sandbox::insert_component: Fix missing peg-net xref update 2022-11-11 20:04:45 -06:00
c6d7e57c59 Keep track of circuit/net states. 2022-11-09 21:47:50 -06:00
935523a414 Update README with more information about version support 2022-11-08 23:02:31 -06:00
70f2a170b6 Create dense allocator for net IDs.
Ensures that the net indexes are a contiguous range, which Logic World assumes.
2022-11-08 22:44:54 -06:00
934055d060 Sandbox: Refactor to use ObjectStore for components/wires
And don't directly map raw IDs to sandbox, reallocate and remap.
2022-11-08 20:01:41 -06:00
df7150ed83 Implement remove_component 2022-11-06 20:05:32 -06:00
5d063859d9 Rename cluster -> net 2022-11-06 19:22:57 -06:00
9 changed files with 847 additions and 239 deletions

View file

@ -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"

View file

@ -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

View file

@ -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
View 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
View file

@ -0,0 +1,4 @@
//! Miscellaneous internal utilities.
pub mod dense_store;
pub mod object_store;

161
src/misc/object_store.rs Normal file
View 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
View 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()
}
}

View file

@ -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()
}

View file

@ -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 {