1
Brewfile.netlify
Normal file
@@ -0,0 +1 @@
|
||||
brew "llvm"
|
||||
104
ic10emu/build.rs
@@ -1,6 +1,6 @@
|
||||
use convert_case::{Case, Casing};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
collections::BTreeSet,
|
||||
env,
|
||||
fmt::Display,
|
||||
fs::{self, File},
|
||||
@@ -9,20 +9,6 @@ use std::{
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
// trait PrimitiveRepr {}
|
||||
// impl PrimitiveRepr for u8 {}
|
||||
// impl PrimitiveRepr for u16 {}
|
||||
// impl PrimitiveRepr for u32 {}
|
||||
// impl PrimitiveRepr for u64 {}
|
||||
// impl PrimitiveRepr for u128 {}
|
||||
// impl PrimitiveRepr for usize {}
|
||||
// impl PrimitiveRepr for i8 {}
|
||||
// impl PrimitiveRepr for i16 {}
|
||||
// impl PrimitiveRepr for i32 {}
|
||||
// impl PrimitiveRepr for i64 {}
|
||||
// impl PrimitiveRepr for i128 {}
|
||||
// impl PrimitiveRepr for isize {}
|
||||
|
||||
struct EnumVariant<P>
|
||||
where
|
||||
P: Display + FromStr,
|
||||
@@ -32,14 +18,14 @@ where
|
||||
pub deprecated: bool,
|
||||
}
|
||||
|
||||
fn write_repr_enum<T: std::io::Write, I, P>(
|
||||
fn write_repr_enum<'a, T: std::io::Write, I, P>(
|
||||
writer: &mut BufWriter<T>,
|
||||
name: &str,
|
||||
variants: &I,
|
||||
variants: I,
|
||||
use_phf: bool,
|
||||
) where
|
||||
P: Display + FromStr,
|
||||
for<'a> &'a I: IntoIterator<Item = (&'a String, &'a EnumVariant<P>)>,
|
||||
P: Display + FromStr + 'a,
|
||||
I: IntoIterator<Item = &'a (String, EnumVariant<P>)>,
|
||||
{
|
||||
let additional_strum = if use_phf { "#[strum(use_phf)]\n" } else { "" };
|
||||
write!(
|
||||
@@ -50,7 +36,7 @@ fn write_repr_enum<T: std::io::Write, I, P>(
|
||||
)
|
||||
.unwrap();
|
||||
for (name, variant) in variants {
|
||||
let variant_name = name.to_case(Case::Pascal);
|
||||
let variant_name = name.replace('.', "").to_case(Case::Pascal);
|
||||
let mut serialize = vec![name.clone()];
|
||||
serialize.extend(variant.aliases.iter().cloned());
|
||||
let serialize_str = serialize
|
||||
@@ -84,7 +70,7 @@ fn write_logictypes() {
|
||||
let output_file = File::create(dest_path).unwrap();
|
||||
let mut writer = BufWriter::new(&output_file);
|
||||
|
||||
let mut logictypes: HashMap<String, EnumVariant<u8>> = HashMap::new();
|
||||
let mut logictypes: Vec<(String, EnumVariant<u8>)> = Vec::new();
|
||||
let l_infile = Path::new("data/logictypes.txt");
|
||||
let l_contents = fs::read_to_string(l_infile).unwrap();
|
||||
|
||||
@@ -106,28 +92,28 @@ fn write_logictypes() {
|
||||
variant.aliases.push(name.to_string());
|
||||
variant.deprecated = deprecated;
|
||||
} else {
|
||||
logictypes.insert(
|
||||
logictypes.push((
|
||||
name.to_string(),
|
||||
EnumVariant {
|
||||
aliases: Vec::new(),
|
||||
value: Some(val),
|
||||
deprecated,
|
||||
},
|
||||
);
|
||||
));
|
||||
}
|
||||
} else {
|
||||
logictypes.insert(
|
||||
logictypes.push((
|
||||
name.to_string(),
|
||||
EnumVariant {
|
||||
aliases: Vec::new(),
|
||||
value: val,
|
||||
deprecated,
|
||||
},
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut slotlogictypes: HashMap<String, EnumVariant<u8>> = HashMap::new();
|
||||
let mut slotlogictypes: Vec<(String, EnumVariant<u8>)> = Vec::new();
|
||||
let sl_infile = Path::new("data/slotlogictypes.txt");
|
||||
let sl_contents = fs::read_to_string(sl_infile).unwrap();
|
||||
|
||||
@@ -149,24 +135,24 @@ fn write_logictypes() {
|
||||
variant.aliases.push(name.to_string());
|
||||
variant.deprecated = deprecated;
|
||||
} else {
|
||||
slotlogictypes.insert(
|
||||
slotlogictypes.push((
|
||||
name.to_string(),
|
||||
EnumVariant {
|
||||
aliases: Vec::new(),
|
||||
value: Some(val),
|
||||
deprecated,
|
||||
},
|
||||
);
|
||||
));
|
||||
}
|
||||
} else {
|
||||
slotlogictypes.insert(
|
||||
slotlogictypes.push((
|
||||
name.to_string(),
|
||||
EnumVariant {
|
||||
aliases: Vec::new(),
|
||||
value: val,
|
||||
deprecated,
|
||||
},
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,31 +172,31 @@ fn write_enums() {
|
||||
let output_file = File::create(dest_path).unwrap();
|
||||
let mut writer = BufWriter::new(&output_file);
|
||||
|
||||
let mut enums_lookup_map_builder = ::phf_codegen::Map::new();
|
||||
let mut check_set = std::collections::HashSet::new();
|
||||
let mut enums_map: Vec<(String, EnumVariant<u16>)> = Vec::new();
|
||||
let e_infile = Path::new("data/enums.txt");
|
||||
let e_contents = fs::read_to_string(e_infile).unwrap();
|
||||
|
||||
for line in e_contents.lines().filter(|l| !l.trim().is_empty()) {
|
||||
let (name, val_str) = line.split_once(' ').unwrap();
|
||||
let mut it = line.splitn(3, ' ');
|
||||
let name = it.next().unwrap();
|
||||
let val_str = it.next().unwrap();
|
||||
let val: Option<u16> = val_str.parse().ok();
|
||||
let docs = it.next();
|
||||
let deprecated = docs
|
||||
.map(|docs| docs.trim().to_uppercase() == "DEPRECATED")
|
||||
.unwrap_or(false);
|
||||
|
||||
let val: Option<u8> = val_str.parse().ok();
|
||||
|
||||
if !check_set.contains(name) {
|
||||
check_set.insert(name);
|
||||
}
|
||||
|
||||
if let Some(v) = val {
|
||||
enums_lookup_map_builder.entry(name, &format!("{}u8", v));
|
||||
}
|
||||
enums_map.push((
|
||||
name.to_string(),
|
||||
EnumVariant {
|
||||
aliases: Vec::new(),
|
||||
value: val,
|
||||
deprecated,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
writeln!(
|
||||
&mut writer,
|
||||
"pub(crate) const ENUM_LOOKUP: phf::Map<&'static str, u8> = {};",
|
||||
enums_lookup_map_builder.build()
|
||||
)
|
||||
.unwrap();
|
||||
write_repr_enum(&mut writer, "LogicEnums", &enums_map, true);
|
||||
|
||||
println!("cargo:rerun-if-changed=data/enums.txt");
|
||||
}
|
||||
@@ -222,7 +208,7 @@ fn write_modes() {
|
||||
let output_file = File::create(dest_path).unwrap();
|
||||
let mut writer = BufWriter::new(&output_file);
|
||||
|
||||
let mut batchmodes: HashMap<String, EnumVariant<u8>> = HashMap::new();
|
||||
let mut batchmodes: Vec<(String, EnumVariant<u8>)> = Vec::new();
|
||||
let b_infile = Path::new("data/batchmodes.txt");
|
||||
let b_contents = fs::read_to_string(b_infile).unwrap();
|
||||
|
||||
@@ -239,28 +225,28 @@ fn write_modes() {
|
||||
{
|
||||
variant.aliases.push(name.to_string());
|
||||
} else {
|
||||
batchmodes.insert(
|
||||
batchmodes.push((
|
||||
name.to_string(),
|
||||
EnumVariant {
|
||||
aliases: Vec::new(),
|
||||
value: Some(val),
|
||||
deprecated: false,
|
||||
},
|
||||
);
|
||||
));
|
||||
}
|
||||
} else {
|
||||
batchmodes.insert(
|
||||
batchmodes.push((
|
||||
name.to_string(),
|
||||
EnumVariant {
|
||||
aliases: Vec::new(),
|
||||
value: val,
|
||||
deprecated: false,
|
||||
},
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut reagentmodes: HashMap<String, EnumVariant<u8>> = HashMap::new();
|
||||
let mut reagentmodes: Vec<(String, EnumVariant<u8>)> = Vec::new();
|
||||
let r_infile = Path::new("data/reagentmodes.txt");
|
||||
let r_contents = fs::read_to_string(r_infile).unwrap();
|
||||
|
||||
@@ -277,24 +263,24 @@ fn write_modes() {
|
||||
{
|
||||
variant.aliases.push(name.to_string());
|
||||
} else {
|
||||
reagentmodes.insert(
|
||||
reagentmodes.push((
|
||||
name.to_string(),
|
||||
EnumVariant {
|
||||
aliases: Vec::new(),
|
||||
value: Some(val),
|
||||
deprecated: false,
|
||||
},
|
||||
);
|
||||
));
|
||||
}
|
||||
} else {
|
||||
reagentmodes.insert(
|
||||
reagentmodes.push((
|
||||
name.to_string(),
|
||||
EnumVariant {
|
||||
aliases: Vec::new(),
|
||||
value: val,
|
||||
deprecated: false,
|
||||
},
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +328,7 @@ fn write_instructions_enum() {
|
||||
let output_file = File::create(dest_path).unwrap();
|
||||
let mut writer = BufWriter::new(&output_file);
|
||||
|
||||
let mut instructions = HashSet::new();
|
||||
let mut instructions = BTreeSet::new();
|
||||
let infile = Path::new("data/instructions.txt");
|
||||
let contents = fs::read_to_string(infile).unwrap();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::interpreter;
|
||||
use crate::interpreter::{self, ICError};
|
||||
use crate::tokens::{SplitConsecutiveIndicesExt, SplitConsecutiveWithIndices};
|
||||
use itertools::Itertools;
|
||||
use std::error::Error;
|
||||
@@ -349,6 +349,7 @@ pub enum Operand {
|
||||
Number(Number),
|
||||
LogicType(LogicType),
|
||||
SlotLogicType(SlotLogicType),
|
||||
LogicOrSlotLogicType(LogicType, SlotLogicType),
|
||||
BatchMode(BatchMode),
|
||||
ReagentMode(ReagentMode),
|
||||
Identifier(Identifier),
|
||||
@@ -369,11 +370,16 @@ impl Operand {
|
||||
Operand::Number(num) => Ok(num.value()),
|
||||
Operand::LogicType(lt) => lt
|
||||
.get_str("value")
|
||||
.map(|val| val.parse::<u8>().unwrap() as f64)
|
||||
.map(|val| val.parse::<u16>().unwrap() as f64)
|
||||
.ok_or(interpreter::ICError::TypeValueNotKnown),
|
||||
Operand::SlotLogicType(slt) => slt
|
||||
.get_str("value")
|
||||
.map(|val| val.parse::<u8>().unwrap() as f64)
|
||||
.map(|val| val.parse::<u16>().unwrap() as f64)
|
||||
.ok_or(interpreter::ICError::TypeValueNotKnown),
|
||||
// default to using LogicType when converting to value
|
||||
Operand::LogicOrSlotLogicType(lt, _) => lt
|
||||
.get_str("value")
|
||||
.map(|val| val.parse::<u16>().unwrap() as f64)
|
||||
.ok_or(interpreter::ICError::TypeValueNotKnown),
|
||||
Operand::BatchMode(bm) => bm
|
||||
.get_str("value")
|
||||
@@ -487,6 +493,32 @@ impl Operand {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_logic_type(
|
||||
&self,
|
||||
ic: &interpreter::IC,
|
||||
inst: InstructionOp,
|
||||
index: u32,
|
||||
) -> Result<LogicType, ICError> {
|
||||
match &self {
|
||||
Operand::LogicType(lt) => Ok(*lt),
|
||||
Operand::LogicOrSlotLogicType(lt, _slt) => Ok(*lt),
|
||||
_ => LogicType::try_from(self.as_value(ic, inst, index)?),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_slot_logic_type(
|
||||
&self,
|
||||
ic: &interpreter::IC,
|
||||
inst: InstructionOp,
|
||||
index: u32,
|
||||
) -> Result<SlotLogicType, ICError> {
|
||||
match &self {
|
||||
Operand::SlotLogicType(slt) => Ok(*slt),
|
||||
Operand::LogicOrSlotLogicType(_lt, slt) => Ok(*slt),
|
||||
_ => SlotLogicType::try_from(self.as_value(ic, inst, index)?),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn translate_alias(&self, ic: &interpreter::IC) -> Self {
|
||||
match &self {
|
||||
Operand::Identifier(id) => {
|
||||
@@ -760,12 +792,22 @@ impl FromStr for Operand {
|
||||
}
|
||||
} else if let Some(val) = CONSTANTS_LOOKUP.get(s) {
|
||||
Ok(Operand::Number(Number::Constant(*val)))
|
||||
} else if let Some(val) = ENUM_LOOKUP.get(s) {
|
||||
Ok(Operand::Number(Number::Enum(*val as f64)))
|
||||
} else if let Ok(val) = LogicEnums::from_str(s) {
|
||||
Ok(Operand::Number(Number::Enum(
|
||||
val.get_str("value").unwrap().parse().unwrap(),
|
||||
)))
|
||||
} else if let Ok(lt) = LogicType::from_str(s) {
|
||||
Ok(Operand::LogicType(lt))
|
||||
if let Ok(slt) = SlotLogicType::from_str(s) {
|
||||
Ok(Operand::LogicOrSlotLogicType(lt, slt))
|
||||
} else {
|
||||
Ok(Operand::LogicType(lt))
|
||||
}
|
||||
} else if let Ok(slt) = SlotLogicType::from_str(s) {
|
||||
Ok(Operand::SlotLogicType(slt))
|
||||
if let Ok(lt) = LogicType::from_str(s) {
|
||||
Ok(Operand::LogicOrSlotLogicType(lt, slt))
|
||||
} else {
|
||||
Ok(Operand::SlotLogicType(slt))
|
||||
}
|
||||
} else if let Ok(bm) = BatchMode::from_str(s) {
|
||||
Ok(Operand::BatchMode(bm))
|
||||
} else if let Ok(rm) = ReagentMode::from_str(s) {
|
||||
@@ -853,6 +895,7 @@ impl Display for Operand {
|
||||
},
|
||||
Operand::LogicType(logic) => Display::fmt(logic, f),
|
||||
Operand::SlotLogicType(slot_logic) => Display::fmt(slot_logic, f),
|
||||
Operand::LogicOrSlotLogicType(logic, _) => Display::fmt(logic, f),
|
||||
Operand::BatchMode(batch_mode) => Display::fmt(batch_mode, f),
|
||||
Operand::ReagentMode(reagent_mode) => Display::fmt(reagent_mode, f),
|
||||
Operand::Identifier(ident) => Display::fmt(&ident, f),
|
||||
@@ -1027,6 +1070,7 @@ mod tests {
|
||||
define a_hash HASH(\"This is a String\")\n\
|
||||
alias a_var r0\n\
|
||||
alias a_device d0\n\
|
||||
s d0 On 1\n\
|
||||
s d0 12 0 \n\
|
||||
move r2 LogicType.Temperature\n\
|
||||
move r3 pinf\n\
|
||||
@@ -1105,6 +1149,20 @@ mod tests {
|
||||
},),),
|
||||
comment: None,
|
||||
},
|
||||
Line {
|
||||
code: Some(Code::Instruction(Instruction {
|
||||
instruction: InstructionOp::S,
|
||||
operands: vec![
|
||||
Operand::DeviceSpec(DeviceSpec {
|
||||
device: Device::Numbered(0),
|
||||
connection: None
|
||||
}),
|
||||
Operand::LogicOrSlotLogicType(LogicType::On, SlotLogicType::On),
|
||||
Operand::Number(Number::Float(1.0))
|
||||
]
|
||||
})),
|
||||
comment: None,
|
||||
},
|
||||
Line {
|
||||
code: Some(Code::Instruction(Instruction {
|
||||
instruction: InstructionOp::S,
|
||||
@@ -1127,9 +1185,7 @@ mod tests {
|
||||
indirection: 0,
|
||||
target: 2,
|
||||
}),
|
||||
Operand::Identifier(Identifier {
|
||||
name: "LogicType.Temperature".to_owned()
|
||||
}),
|
||||
Operand::Number(Number::Enum(6.0)),
|
||||
],
|
||||
},),),
|
||||
comment: None,
|
||||
@@ -1295,10 +1351,10 @@ mod tests {
|
||||
test_roundtrip("42");
|
||||
test_roundtrip("1.2345");
|
||||
test_roundtrip("-1.2345");
|
||||
test_roundtrip(&LogicType::Pressure.to_string());
|
||||
test_roundtrip(&SlotLogicType::Occupied.to_string());
|
||||
test_roundtrip(&BatchMode::Average.to_string());
|
||||
test_roundtrip(&ReagentMode::Recipe.to_string());
|
||||
test_roundtrip(LogicType::Pressure.as_ref());
|
||||
test_roundtrip(SlotLogicType::Occupied.as_ref());
|
||||
test_roundtrip(BatchMode::Average.as_ref());
|
||||
test_roundtrip(ReagentMode::Recipe.as_ref());
|
||||
test_roundtrip("pi");
|
||||
test_roundtrip("pinf");
|
||||
test_roundtrip("ninf");
|
||||
|
||||
@@ -32,81 +32,83 @@ impl Error for LineError {}
|
||||
|
||||
#[derive(Debug, Error, Clone, Serialize, Deserialize)]
|
||||
pub enum ICError {
|
||||
#[error("Error Compiling Code: {0}")]
|
||||
#[error("error compiling code: {0}")]
|
||||
ParseError(#[from] ParseError),
|
||||
#[error("Duplicate label {0}")]
|
||||
#[error("duplicate label {0}")]
|
||||
DuplicateLabel(String),
|
||||
#[error("Instruction Pointer out of range: '{0}'")]
|
||||
#[error("instruction pointer out of range: '{0}'")]
|
||||
InstructionPointerOutOfRange(u32),
|
||||
#[error("Register Pointer out of range: '{0}'")]
|
||||
#[error("register pointer out of range: '{0}'")]
|
||||
RegisterIndexOutOfRange(f64),
|
||||
#[error("Device Pointer out of range: '{0}'")]
|
||||
#[error("device pointer out of range: '{0}'")]
|
||||
DeviceIndexOutOfRange(f64),
|
||||
#[error("Stack index out of range: '{0}'")]
|
||||
#[error("stack index out of range: '{0}'")]
|
||||
StackIndexOutOfRange(f64),
|
||||
#[error("slot index out of range: '{0}'")]
|
||||
SlotIndexOutOfRange(f64),
|
||||
#[error("Unknown device ID '{0}'")]
|
||||
#[error("pin index {0} out of range 0-6")]
|
||||
PinIndexOutOfRange(usize),
|
||||
#[error("connection index {0} out of range {1}")]
|
||||
ConnectionIndexOutOfRange(usize, usize),
|
||||
#[error("unknown device ID '{0}'")]
|
||||
UnknownDeviceID(f64),
|
||||
#[error("Too few operands!: provide: '{provided}', desired: '{desired}'")]
|
||||
#[error("too few operands!: provide: '{provided}', desired: '{desired}'")]
|
||||
TooFewOperands { provided: u32, desired: u32 },
|
||||
#[error("Too many operands!: provide: '{provided}', desired: '{desired}'")]
|
||||
#[error("too many operands!: provide: '{provided}', desired: '{desired}'")]
|
||||
TooManyOperands { provided: u32, desired: u32 },
|
||||
#[error("Incorrect Operand Type for instruction `{inst}` operand {index}, not a {desired} ")]
|
||||
#[error("incorrect operand type for instruction `{inst}` operand {index}, not a {desired} ")]
|
||||
IncorrectOperandType {
|
||||
inst: grammar::InstructionOp,
|
||||
index: u32,
|
||||
desired: String,
|
||||
},
|
||||
#[error("Unknown identifier '{0}")]
|
||||
#[error("unknown identifier {0}")]
|
||||
UnknownIdentifier(String),
|
||||
#[error("Device Not Set")]
|
||||
#[error("device Not Set")]
|
||||
DeviceNotSet,
|
||||
#[error("Shift Underflow i64(signed long)")]
|
||||
#[error("shift Underflow i64(signed long)")]
|
||||
ShiftUnderflowI64,
|
||||
#[error("Shift Overflow i64(signed long)")]
|
||||
#[error("shift Overflow i64(signed long)")]
|
||||
ShiftOverflowI64,
|
||||
#[error("Shift Underflow i32(signed int)")]
|
||||
#[error("shift underflow i32(signed int)")]
|
||||
ShiftUnderflowI32,
|
||||
#[error("Shift Overflow i32(signed int)")]
|
||||
#[error("shift overflow i32(signed int)")]
|
||||
ShiftOverflowI32,
|
||||
#[error("Stack Underflow")]
|
||||
#[error("stack underflow")]
|
||||
StackUnderflow,
|
||||
#[error("Stack Overflow")]
|
||||
#[error("stack overflow")]
|
||||
StackOverflow,
|
||||
#[error("Duplicate Define '{0}'")]
|
||||
#[error("duplicate define '{0}'")]
|
||||
DuplicateDefine(String),
|
||||
#[error("Read Only field '{0}'")]
|
||||
#[error("read only field '{0}'")]
|
||||
ReadOnlyField(String),
|
||||
#[error("Write Only field '{0}'")]
|
||||
#[error("write only field '{0}'")]
|
||||
WriteOnlyField(String),
|
||||
#[error("Device Has No Field '{0}'")]
|
||||
#[error("device has no field '{0}'")]
|
||||
DeviceHasNoField(String),
|
||||
#[error("Device has not IC")]
|
||||
#[error("device has not ic")]
|
||||
DeviceHasNoIC,
|
||||
#[error("Unknown Device '{0}'")]
|
||||
UnknownDeviceId(f64),
|
||||
#[error("Unknown Logic Type '{0}'")]
|
||||
#[error("unknown device '{0}'")]
|
||||
unknownDeviceId(f64),
|
||||
#[error("unknown logic type '{0}'")]
|
||||
UnknownLogicType(f64),
|
||||
#[error("Unknown Slot Logic Type '{0}'")]
|
||||
#[error("unknown slot logic type '{0}'")]
|
||||
UnknownSlotLogicType(f64),
|
||||
#[error("Unknown Batch Mode '{0}'")]
|
||||
#[error("unknown batch mode '{0}'")]
|
||||
UnknownBatchMode(f64),
|
||||
#[error("Unknown Reagent Mode '{0}'")]
|
||||
#[error("unknown reagent mode '{0}'")]
|
||||
UnknownReagentMode(f64),
|
||||
#[error("Type Value Not Known")]
|
||||
#[error("type value not known")]
|
||||
TypeValueNotKnown,
|
||||
#[error("Empty Device List")]
|
||||
#[error("empty device list")]
|
||||
EmptyDeviceList,
|
||||
#[error("Connection index out of range: '{0}'")]
|
||||
ConnectionIndexOutOFRange(u32),
|
||||
#[error("Connection specifier missing")]
|
||||
#[error("connection specifier missing")]
|
||||
MissingConnectionSpecifier,
|
||||
#[error("No data network on connection '{0}'")]
|
||||
NotDataConnection(u32),
|
||||
#[error("Network not connected on connection '{0}'")]
|
||||
NetworkNotConnected(u32),
|
||||
#[error("Bad Network Id '{0}'")]
|
||||
#[error("no data network on connection '{0}'")]
|
||||
NotDataConnection(usize),
|
||||
#[error("network not connected on connection '{0}'")]
|
||||
NetworkNotConnected(usize),
|
||||
#[error("bad network Id '{0}'")]
|
||||
BadNetworkId(u32),
|
||||
}
|
||||
|
||||
@@ -2030,9 +2032,9 @@ impl IC {
|
||||
let RegisterSpec {
|
||||
indirection,
|
||||
target,
|
||||
|
||||
} = reg.as_register(this, inst, 1)?;
|
||||
let (Some(device_id), _connection) = dev_id.as_device(this, inst, 2)? else {
|
||||
let (Some(device_id), _connection) = dev_id.as_device(this, inst, 2)?
|
||||
else {
|
||||
return Err(DeviceNotSet);
|
||||
};
|
||||
let device = vm.get_device_same_network(this.device, device_id);
|
||||
@@ -2057,9 +2059,9 @@ impl IC {
|
||||
let RegisterSpec {
|
||||
indirection,
|
||||
target,
|
||||
|
||||
} = reg.as_register(this, inst, 1)?;
|
||||
let (Some(device_id), _connection) = dev_id.as_device(this, inst, 2)? else {
|
||||
let (Some(device_id), _connection) = dev_id.as_device(this, inst, 2)?
|
||||
else {
|
||||
return Err(DeviceNotSet);
|
||||
};
|
||||
let device = vm.get_device_same_network(this.device, device_id);
|
||||
@@ -2080,9 +2082,9 @@ impl IC {
|
||||
oprs => Err(ICError::mismatch_operands(oprs.len(), 3)),
|
||||
},
|
||||
Put => match &operands[..] {
|
||||
|
||||
[dev_id, addr, val] => {
|
||||
let (Some(device_id), _connection) = dev_id.as_device(this, inst, 1)? else {
|
||||
let (Some(device_id), _connection) = dev_id.as_device(this, inst, 1)?
|
||||
else {
|
||||
return Err(DeviceNotSet);
|
||||
};
|
||||
let device = vm.get_device_same_network(this.device, device_id);
|
||||
@@ -2093,6 +2095,7 @@ impl IC {
|
||||
let val = val.as_value(this, inst, 3)?;
|
||||
let mut ic = vm.ics.get(ic_id).unwrap().borrow_mut();
|
||||
ic.poke(addr, val)?;
|
||||
vm.set_modified(device_id);
|
||||
Ok(())
|
||||
}
|
||||
None => Err(DeviceHasNoIC),
|
||||
@@ -2116,6 +2119,7 @@ impl IC {
|
||||
let val = val.as_value(this, inst, 3)?;
|
||||
let mut ic = vm.ics.get(ic_id).unwrap().borrow_mut();
|
||||
ic.poke(addr, val)?;
|
||||
vm.set_modified(device_id as u16);
|
||||
Ok(())
|
||||
}
|
||||
None => Err(DeviceHasNoIC),
|
||||
@@ -2131,7 +2135,7 @@ impl IC {
|
||||
let (Some(device_id), connection) = dev.as_device(this, inst, 1)? else {
|
||||
return Err(DeviceNotSet);
|
||||
};
|
||||
let lt = LogicType::try_from(lt.as_value(this, inst, 2)?)?;
|
||||
let lt = lt.as_logic_type(this, inst, 2)?;
|
||||
if CHANNEL_LOGIC_TYPES.contains(<) {
|
||||
let channel = lt.as_channel().unwrap();
|
||||
let Some(connection) = connection else {
|
||||
@@ -2150,6 +2154,7 @@ impl IC {
|
||||
Some(device) => {
|
||||
let val = val.as_value(this, inst, 1)?;
|
||||
device.borrow_mut().set_field(lt, val)?;
|
||||
vm.set_modified(device_id);
|
||||
Ok(())
|
||||
}
|
||||
None => Err(UnknownDeviceID(device_id as f64)),
|
||||
@@ -2166,9 +2171,10 @@ impl IC {
|
||||
let device = vm.get_device_same_network(this.device, device_id as u16);
|
||||
match device {
|
||||
Some(device) => {
|
||||
let lt = LogicType::try_from(lt.as_value(this, inst, 2)?)?;
|
||||
let lt = lt.as_logic_type(this, inst, 2)?;
|
||||
let val = val.as_value(this, inst, 3)?;
|
||||
device.borrow_mut().set_field(lt, val)?;
|
||||
vm.set_modified(device_id as u16);
|
||||
Ok(())
|
||||
}
|
||||
None => Err(UnknownDeviceID(device_id)),
|
||||
@@ -2177,7 +2183,7 @@ impl IC {
|
||||
oprs => Err(ICError::mismatch_operands(oprs.len(), 3)),
|
||||
},
|
||||
Ss => match &operands[..] {
|
||||
[dev, index, lt, val] => {
|
||||
[dev, index, slt, val] => {
|
||||
let (Some(device_id), _connection) = dev.as_device(this, inst, 1)? else {
|
||||
return Err(DeviceNotSet);
|
||||
};
|
||||
@@ -2185,9 +2191,10 @@ impl IC {
|
||||
match device {
|
||||
Some(device) => {
|
||||
let index = index.as_value(this, inst, 2)?;
|
||||
let lt = SlotLogicType::try_from(lt.as_value(this, inst, 3)?)?;
|
||||
let slt = slt.as_slot_logic_type(this, inst, 3)?;
|
||||
let val = val.as_value(this, inst, 4)?;
|
||||
device.borrow_mut().set_slot_field(index, lt, val)?;
|
||||
device.borrow_mut().set_slot_field(index, slt, val)?;
|
||||
vm.set_modified(device_id);
|
||||
Ok(())
|
||||
}
|
||||
None => Err(UnknownDeviceID(device_id as f64)),
|
||||
@@ -2198,7 +2205,7 @@ impl IC {
|
||||
Sb => match &operands[..] {
|
||||
[prefab, lt, val] => {
|
||||
let prefab = prefab.as_value(this, inst, 1)?;
|
||||
let lt = LogicType::try_from(lt.as_value(this, inst, 2)?)?;
|
||||
let lt = lt.as_logic_type(this, inst, 2)?;
|
||||
let val = val.as_value(this, inst, 3)?;
|
||||
vm.set_batch_device_field(this.device, prefab, lt, val)?;
|
||||
Ok(())
|
||||
@@ -2206,12 +2213,12 @@ impl IC {
|
||||
oprs => Err(ICError::mismatch_operands(oprs.len(), 3)),
|
||||
},
|
||||
Sbs => match &operands[..] {
|
||||
[prefab, index, lt, val] => {
|
||||
[prefab, index, slt, val] => {
|
||||
let prefab = prefab.as_value(this, inst, 1)?;
|
||||
let index = index.as_value(this, inst, 2)?;
|
||||
let lt = SlotLogicType::try_from(lt.as_value(this, inst, 3)?)?;
|
||||
let slt = slt.as_slot_logic_type(this, inst, 3)?;
|
||||
let val = val.as_value(this, inst, 4)?;
|
||||
vm.set_batch_device_slot_field(this.device, prefab, index, lt, val)?;
|
||||
vm.set_batch_device_slot_field(this.device, prefab, index, slt, val)?;
|
||||
Ok(())
|
||||
}
|
||||
oprs => Err(ICError::mismatch_operands(oprs.len(), 4)),
|
||||
@@ -2220,7 +2227,7 @@ impl IC {
|
||||
[prefab, name, lt, val] => {
|
||||
let prefab = prefab.as_value(this, inst, 1)?;
|
||||
let name = name.as_value(this, inst, 2)?;
|
||||
let lt = LogicType::try_from(lt.as_value(this, inst, 3)?)?;
|
||||
let lt = lt.as_logic_type(this, inst, 3)?;
|
||||
let val = val.as_value(this, inst, 4)?;
|
||||
vm.set_batch_name_device_field(this.device, prefab, name, lt, val)?;
|
||||
Ok(())
|
||||
@@ -2237,7 +2244,7 @@ impl IC {
|
||||
let (Some(device_id), connection) = dev.as_device(this, inst, 2)? else {
|
||||
return Err(DeviceNotSet);
|
||||
};
|
||||
let lt = LogicType::try_from(lt.as_value(this, inst, 3)?)?;
|
||||
let lt = lt.as_logic_type(this, inst, 3)?;
|
||||
if CHANNEL_LOGIC_TYPES.contains(<) {
|
||||
let channel = lt.as_channel().unwrap();
|
||||
let Some(connection) = connection else {
|
||||
@@ -2276,7 +2283,7 @@ impl IC {
|
||||
let device = vm.get_device_same_network(this.device, device_id as u16);
|
||||
match device {
|
||||
Some(device) => {
|
||||
let lt = LogicType::try_from(lt.as_value(this, inst, 3)?)?;
|
||||
let lt = lt.as_logic_type(this, inst, 3)?;
|
||||
let val = device.borrow().get_field(lt)?;
|
||||
this.set_register(indirection, target, val)?;
|
||||
Ok(())
|
||||
@@ -2287,7 +2294,7 @@ impl IC {
|
||||
oprs => Err(ICError::mismatch_operands(oprs.len(), 3)),
|
||||
},
|
||||
Ls => match &operands[..] {
|
||||
[reg, dev, index, lt] => {
|
||||
[reg, dev, index, slt] => {
|
||||
let RegisterSpec {
|
||||
indirection,
|
||||
target,
|
||||
@@ -2299,8 +2306,8 @@ impl IC {
|
||||
match device {
|
||||
Some(device) => {
|
||||
let index = index.as_value(this, inst, 3)?;
|
||||
let lt = SlotLogicType::try_from(lt.as_value(this, inst, 4)?)?;
|
||||
let val = device.borrow().get_slot_field(index, lt)?;
|
||||
let slt = slt.as_slot_logic_type(this, inst, 4)?;
|
||||
let val = device.borrow().get_slot_field(index, slt)?;
|
||||
this.set_register(indirection, target, val)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -2339,7 +2346,7 @@ impl IC {
|
||||
target,
|
||||
} = reg.as_register(this, inst, 1)?;
|
||||
let prefab = prefab.as_value(this, inst, 2)?;
|
||||
let lt = LogicType::try_from(lt.as_value(this, inst, 3)?)?;
|
||||
let lt = lt.as_logic_type(this, inst, 3)?;
|
||||
let bm = BatchMode::try_from(bm.as_value(this, inst, 4)?)?;
|
||||
let val = vm.get_batch_device_field(this.device, prefab, lt, bm)?;
|
||||
this.set_register(indirection, target, val)?;
|
||||
@@ -2355,7 +2362,7 @@ impl IC {
|
||||
} = reg.as_register(this, inst, 1)?;
|
||||
let prefab = prefab.as_value(this, inst, 2)?;
|
||||
let name = name.as_value(this, inst, 3)?;
|
||||
let lt = LogicType::try_from(lt.as_value(this, inst, 4)?)?;
|
||||
let lt = lt.as_logic_type(this, inst, 4)?;
|
||||
let bm = BatchMode::try_from(bm.as_value(this, inst, 5)?)?;
|
||||
let val =
|
||||
vm.get_batch_name_device_field(this.device, prefab, name, lt, bm)?;
|
||||
@@ -2373,7 +2380,7 @@ impl IC {
|
||||
let prefab = prefab.as_value(this, inst, 2)?;
|
||||
let name = name.as_value(this, inst, 3)?;
|
||||
let index = index.as_value(this, inst, 4)?;
|
||||
let slt = SlotLogicType::try_from(slt.as_value(this, inst, 5)?)?;
|
||||
let slt = slt.as_slot_logic_type(this, inst, 5)?;
|
||||
let bm = BatchMode::try_from(bm.as_value(this, inst, 6)?)?;
|
||||
let val = vm.get_batch_name_device_slot_field(
|
||||
this.device,
|
||||
@@ -2396,7 +2403,7 @@ impl IC {
|
||||
} = reg.as_register(this, inst, 1)?;
|
||||
let prefab = prefab.as_value(this, inst, 2)?;
|
||||
let index = index.as_value(this, inst, 3)?;
|
||||
let slt = SlotLogicType::try_from(slt.as_value(this, inst, 4)?)?;
|
||||
let slt = slt.as_slot_logic_type(this, inst, 4)?;
|
||||
let bm = BatchMode::try_from(bm.as_value(this, inst, 5)?)?;
|
||||
let val =
|
||||
vm.get_batch_device_slot_field(this.device, prefab, index, slt, bm)?;
|
||||
@@ -2410,8 +2417,8 @@ impl IC {
|
||||
let result = process_op(self);
|
||||
if result.is_ok() || advance_ip_on_err {
|
||||
self.ic += 1;
|
||||
self.ip = next_ip;
|
||||
}
|
||||
self.ip = next_ip;
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,18 +18,20 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, Serialize, Deserialize)]
|
||||
pub enum VMError {
|
||||
#[error("Device with id '{0}' does not exist")]
|
||||
#[error("device with id '{0}' does not exist")]
|
||||
UnknownId(u16),
|
||||
#[error("IC with id '{0}' does not exist")]
|
||||
#[error("ic with id '{0}' does not exist")]
|
||||
UnknownIcId(u16),
|
||||
#[error("Device with id '{0}' does not have a IC Slot")]
|
||||
#[error("device with id '{0}' does not have a ic slot")]
|
||||
NoIC(u16),
|
||||
#[error("IC encountered an error: {0}")]
|
||||
#[error("ic encountered an error: {0}")]
|
||||
ICError(#[from] ICError),
|
||||
#[error("IC encountered an error: {0}")]
|
||||
#[error("ic encountered an error: {0}")]
|
||||
LineError(#[from] LineError),
|
||||
#[error("Invalid network ID {0}")]
|
||||
#[error("invalid network id {0}")]
|
||||
InvalidNetwork(u16),
|
||||
#[error("device {0} not visible to device {1} (not on the same networks)")]
|
||||
DeviceNotVisible(u16, u16),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -48,6 +50,9 @@ pub struct LogicField {
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct Slot {
|
||||
pub typ: SlotType,
|
||||
// FIXME: this actualy needs to be an "Occupant" field
|
||||
// where the Occupant is an items with a
|
||||
// quantity, PrefabName/Hash, fields, etc
|
||||
pub fields: HashMap<grammar::SlotLogicType, LogicField>,
|
||||
}
|
||||
|
||||
@@ -169,7 +174,7 @@ pub struct Device {
|
||||
pub slots: Vec<Slot>,
|
||||
pub reagents: HashMap<ReagentMode, HashMap<i32, f64>>,
|
||||
pub ic: Option<u16>,
|
||||
pub connections: [Connection; 8],
|
||||
pub connections: Vec<Connection>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -200,6 +205,9 @@ pub struct VM {
|
||||
id_gen: IdSequenceGenerator,
|
||||
network_id_gen: IdSequenceGenerator,
|
||||
random: Rc<RefCell<crate::rand_mscorlib::Random>>,
|
||||
|
||||
/// list of device id's touched on the last operation
|
||||
operation_modified: RefCell<Vec<u16>>,
|
||||
}
|
||||
|
||||
impl Default for Network {
|
||||
@@ -261,55 +269,123 @@ impl Device {
|
||||
slots: Vec::new(),
|
||||
reagents: HashMap::new(),
|
||||
ic: None,
|
||||
connections: [Connection::default(); 8],
|
||||
connections: vec![Connection::CableNetwork(None)],
|
||||
};
|
||||
device.connections[0] = Connection::CableNetwork(None);
|
||||
device.fields.insert(
|
||||
LogicType::ReferenceId,
|
||||
LogicField {
|
||||
field_type: FieldType::Read,
|
||||
value: id as f64,
|
||||
},
|
||||
);
|
||||
device
|
||||
}
|
||||
|
||||
pub fn with_ic(id: u16, ic: u16) -> Self {
|
||||
let mut device = Device::new(id);
|
||||
device.ic = Some(ic);
|
||||
device.fields.insert(
|
||||
LogicType::Setting,
|
||||
LogicField {
|
||||
field_type: FieldType::ReadWrite,
|
||||
value: 0.0,
|
||||
},
|
||||
);
|
||||
device.connections = vec![Connection::CableNetwork(None), Connection::Other];
|
||||
device.prefab_name = Some("StructureCircuitHousing".to_owned());
|
||||
device.fields.insert(
|
||||
LogicType::Error,
|
||||
LogicField {
|
||||
field_type: FieldType::ReadWrite,
|
||||
value: 0.0,
|
||||
},
|
||||
);
|
||||
device.prefab_hash = Some(-128473777);
|
||||
device.fields.insert(
|
||||
LogicType::PrefabHash,
|
||||
LogicField {
|
||||
field_type: FieldType::Read,
|
||||
value: -128473777.0,
|
||||
},
|
||||
);
|
||||
device.fields.extend(vec![
|
||||
(
|
||||
LogicType::Power,
|
||||
LogicField {
|
||||
field_type: FieldType::Read,
|
||||
value: 1.0,
|
||||
},
|
||||
),
|
||||
(
|
||||
LogicType::Error,
|
||||
LogicField {
|
||||
field_type: FieldType::ReadWrite,
|
||||
value: 0.0,
|
||||
},
|
||||
),
|
||||
(
|
||||
LogicType::Setting,
|
||||
LogicField {
|
||||
field_type: FieldType::ReadWrite,
|
||||
value: 0.0,
|
||||
},
|
||||
),
|
||||
(
|
||||
LogicType::On,
|
||||
LogicField {
|
||||
field_type: FieldType::ReadWrite,
|
||||
value: 0.0,
|
||||
},
|
||||
),
|
||||
(
|
||||
LogicType::RequiredPower,
|
||||
LogicField {
|
||||
field_type: FieldType::Read,
|
||||
value: 0.0,
|
||||
},
|
||||
),
|
||||
(
|
||||
LogicType::PrefabHash,
|
||||
LogicField {
|
||||
field_type: FieldType::Read,
|
||||
value: -128473777.0,
|
||||
},
|
||||
),
|
||||
(
|
||||
LogicType::LineNumber,
|
||||
LogicField {
|
||||
field_type: FieldType::ReadWrite,
|
||||
value: 0.0,
|
||||
},
|
||||
),
|
||||
(
|
||||
LogicType::ReferenceId,
|
||||
LogicField {
|
||||
field_type: FieldType::Read,
|
||||
value: id as f64,
|
||||
},
|
||||
),
|
||||
]);
|
||||
device.slots.push(Slot {
|
||||
typ: SlotType::ProgramableChip,
|
||||
fields: HashMap::from([
|
||||
(
|
||||
SlotLogicType::PrefabHash,
|
||||
LogicField {
|
||||
field_type: FieldType::Read,
|
||||
value: -744098481.0,
|
||||
},
|
||||
),
|
||||
(
|
||||
SlotLogicType::LineNumber,
|
||||
LogicField {
|
||||
field_type: FieldType::Read,
|
||||
value: 0.0,
|
||||
},
|
||||
),
|
||||
]),
|
||||
});
|
||||
|
||||
device
|
||||
}
|
||||
|
||||
pub fn get_network_id(&self, connection: usize) -> Result<u16, ICError> {
|
||||
if connection >= 8 {
|
||||
Err(ICError::ConnectionIndexOutOFRange(connection as u32))
|
||||
if connection >= self.connections.len() {
|
||||
Err(ICError::ConnectionIndexOutOfRange(
|
||||
connection,
|
||||
self.connections.len(),
|
||||
))
|
||||
} else if let Connection::CableNetwork(network_id) = self.connections[connection] {
|
||||
if let Some(network_id) = network_id {
|
||||
Ok(network_id)
|
||||
} else {
|
||||
Err(ICError::NetworkNotConnected(connection as u32))
|
||||
Err(ICError::NetworkNotConnected(connection))
|
||||
}
|
||||
} else {
|
||||
Err(ICError::NotDataConnection(connection as u32))
|
||||
Err(ICError::NotDataConnection(connection))
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: this needs some logic to link some special fields like "LineNumber" to the chip
|
||||
pub fn get_field(&self, typ: grammar::LogicType) -> Result<f64, ICError> {
|
||||
if let Some(field) = self.fields.get(&typ) {
|
||||
if field.field_type == FieldType::Read || field.field_type == FieldType::ReadWrite {
|
||||
@@ -322,6 +398,7 @@ impl Device {
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: this needs some logic to link some special fields like "LineNumber" to the chip
|
||||
pub fn set_field(&mut self, typ: grammar::LogicType, val: f64) -> Result<(), ICError> {
|
||||
if let Some(field) = self.fields.get_mut(&typ) {
|
||||
if field.field_type == FieldType::Write || field.field_type == FieldType::ReadWrite {
|
||||
@@ -335,6 +412,7 @@ impl Device {
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: this needs to work with slot Occupants, see `Slot` decl
|
||||
pub fn get_slot_field(&self, index: f64, typ: grammar::SlotLogicType) -> Result<f64, ICError> {
|
||||
if let Some(field) = self
|
||||
.slots
|
||||
@@ -353,6 +431,7 @@ impl Device {
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: this needs to work with slot Occupants, see `Slot` decl
|
||||
pub fn set_slot_field(
|
||||
&mut self,
|
||||
index: f64,
|
||||
@@ -385,6 +464,11 @@ impl Device {
|
||||
}
|
||||
0.0
|
||||
}
|
||||
|
||||
pub fn set_name(&mut self, name: &str) {
|
||||
self.name_hash = Some((const_crc32::crc32(name.as_bytes()) as i32).into());
|
||||
self.name = Some(name.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VM {
|
||||
@@ -410,6 +494,7 @@ impl VM {
|
||||
id_gen,
|
||||
network_id_gen,
|
||||
random: Rc::new(RefCell::new(crate::rand_mscorlib::Random::new())),
|
||||
operation_modified: RefCell::new(Vec::new()),
|
||||
};
|
||||
let _ = vm.add_ic(None);
|
||||
vm
|
||||
@@ -447,15 +532,27 @@ impl VM {
|
||||
});
|
||||
}
|
||||
let id = device.id;
|
||||
|
||||
let first_data_network = device
|
||||
.connections
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(index, conn)| match conn {
|
||||
Connection::CableNetwork(_) => Some(index),
|
||||
Connection::Other => None,
|
||||
});
|
||||
self.devices.insert(id, Rc::new(RefCell::new(device)));
|
||||
let _ = self.add_device_to_network(
|
||||
id,
|
||||
if let Some(network) = network {
|
||||
network
|
||||
} else {
|
||||
self.default_network
|
||||
},
|
||||
);
|
||||
if let Some(first_data_network) = first_data_network {
|
||||
let _ = self.add_device_to_network(
|
||||
id,
|
||||
if let Some(network) = network {
|
||||
network
|
||||
} else {
|
||||
self.default_network
|
||||
},
|
||||
first_data_network,
|
||||
);
|
||||
}
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
@@ -481,16 +578,27 @@ impl VM {
|
||||
}
|
||||
let id = device.id;
|
||||
let ic_id = ic.id;
|
||||
let first_data_network = device
|
||||
.connections
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(index, conn)| match conn {
|
||||
Connection::CableNetwork(_) => Some(index),
|
||||
Connection::Other => None,
|
||||
});
|
||||
self.devices.insert(id, Rc::new(RefCell::new(device)));
|
||||
self.ics.insert(ic_id, Rc::new(RefCell::new(ic)));
|
||||
let _ = self.add_device_to_network(
|
||||
id,
|
||||
if let Some(network) = network {
|
||||
network
|
||||
} else {
|
||||
self.default_network
|
||||
},
|
||||
);
|
||||
if let Some(first_data_network) = first_data_network {
|
||||
let _ = self.add_device_to_network(
|
||||
id,
|
||||
if let Some(network) = network {
|
||||
network
|
||||
} else {
|
||||
self.default_network
|
||||
},
|
||||
first_data_network,
|
||||
);
|
||||
}
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
@@ -555,9 +663,16 @@ impl VM {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// returns a list of device ids modified in the last operations
|
||||
pub fn last_operation_modified(&self) -> Vec<u16> {
|
||||
self.operation_modified.borrow().clone()
|
||||
}
|
||||
|
||||
pub fn step_ic(&self, id: u16, advance_ip_on_err: bool) -> Result<bool, VMError> {
|
||||
self.operation_modified.borrow_mut().clear();
|
||||
let device = self.devices.get(&id).ok_or(VMError::UnknownId(id))?.clone();
|
||||
let ic_id = *device.borrow().ic.as_ref().ok_or(VMError::NoIC(id))?;
|
||||
self.set_modified(id);
|
||||
let ic = self
|
||||
.ics
|
||||
.get(&ic_id)
|
||||
@@ -570,6 +685,7 @@ impl VM {
|
||||
|
||||
/// returns true if executed 128 lines, false if returned early.
|
||||
pub fn run_ic(&self, id: u16, ignore_errors: bool) -> Result<bool, VMError> {
|
||||
self.operation_modified.borrow_mut().clear();
|
||||
let device = self.devices.get(&id).ok_or(VMError::UnknownId(id))?.clone();
|
||||
let ic_id = *device.borrow().ic.as_ref().ok_or(VMError::NoIC(id))?;
|
||||
let ic = self
|
||||
@@ -578,6 +694,7 @@ impl VM {
|
||||
.ok_or(VMError::UnknownIcId(ic_id))?
|
||||
.clone();
|
||||
ic.borrow_mut().ic = 0;
|
||||
self.set_modified(id);
|
||||
for _i in 0..128 {
|
||||
if let Err(err) = ic.borrow_mut().step(self, ignore_errors) {
|
||||
if !ignore_errors {
|
||||
@@ -594,6 +711,10 @@ impl VM {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn set_modified(&self, id: u16) {
|
||||
self.operation_modified.borrow_mut().push(id);
|
||||
}
|
||||
|
||||
pub fn reset_ic(&self, id: u16) -> Result<bool, VMError> {
|
||||
let device = self.devices.get(&id).ok_or(VMError::UnknownId(id))?.clone();
|
||||
let ic_id = *device.borrow().ic.as_ref().ok_or(VMError::NoIC(id))?;
|
||||
@@ -665,11 +786,71 @@ impl VM {
|
||||
false
|
||||
}
|
||||
|
||||
fn add_device_to_network(&self, id: u16, network_id: u16) -> Result<bool, VMError> {
|
||||
if !self.devices.contains_key(&id) {
|
||||
/// return a vecter with the device ids the source id can see via it's connected netowrks
|
||||
pub fn visible_devices(&self, source: u16) -> Vec<u16> {
|
||||
self.networks
|
||||
.values()
|
||||
.filter_map(|net| {
|
||||
if net.borrow().contains(&[source]) {
|
||||
Some(
|
||||
net.borrow()
|
||||
.devices
|
||||
.iter()
|
||||
.filter(|id| id != &&source)
|
||||
.copied()
|
||||
.collect_vec(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.concat()
|
||||
}
|
||||
|
||||
pub fn set_pin(&self, id: u16, pin: usize, val: Option<u16>) -> Result<bool, VMError> {
|
||||
let Some(device) = self.devices.get(&id) else {
|
||||
return Err(VMError::UnknownId(id));
|
||||
};
|
||||
if let Some(other_device) = val {
|
||||
if !self.devices.contains_key(&other_device) {
|
||||
return Err(VMError::UnknownId(other_device));
|
||||
}
|
||||
if !self.devices_on_same_network(&[id, other_device]) {
|
||||
return Err(VMError::DeviceNotVisible(other_device, id));
|
||||
}
|
||||
}
|
||||
if !(0..6).contains(&pin) {
|
||||
Err(ICError::PinIndexOutOfRange(pin).into())
|
||||
} else {
|
||||
let Some(ic_id) = device.borrow().ic else {
|
||||
return Err(VMError::NoIC(id));
|
||||
};
|
||||
self.ics.get(&ic_id).unwrap().borrow_mut().pins[pin] = val;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_device_to_network(
|
||||
&self,
|
||||
id: u16,
|
||||
network_id: u16,
|
||||
connection: usize,
|
||||
) -> Result<bool, VMError> {
|
||||
if let Some(network) = self.networks.get(&network_id) {
|
||||
let Some(device) = self.devices.get(&id) else {
|
||||
return Err(VMError::UnknownId(id));
|
||||
};
|
||||
if connection >= device.borrow().connections.len() {
|
||||
let conn_len = device.borrow().connections.len();
|
||||
return Err(ICError::ConnectionIndexOutOfRange(connection, conn_len).into());
|
||||
}
|
||||
let Connection::CableNetwork(ref mut conn) =
|
||||
device.borrow_mut().connections[connection]
|
||||
else {
|
||||
return Err(ICError::NotDataConnection(connection).into());
|
||||
};
|
||||
*conn = Some(network_id);
|
||||
|
||||
network.borrow_mut().add(id);
|
||||
Ok(true)
|
||||
} else {
|
||||
@@ -677,6 +858,27 @@ impl VM {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_device_from_network(&self, id: u16, network_id: u16) -> Result<bool, VMError> {
|
||||
if let Some(network) = self.networks.get(&network_id) {
|
||||
let Some(device) = self.devices.get(&id) else {
|
||||
return Err(VMError::UnknownId(id));
|
||||
};
|
||||
let mut device_ref = device.borrow_mut();
|
||||
|
||||
for conn in device_ref.connections.iter_mut() {
|
||||
if let Connection::CableNetwork(conn) = conn {
|
||||
if conn.is_some_and(|id| id == network_id) {
|
||||
*conn = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
network.borrow_mut().remove(id);
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(VMError::InvalidNetwork(network_id))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_batch_device_field(
|
||||
&self,
|
||||
source: u16,
|
||||
@@ -685,7 +887,10 @@ impl VM {
|
||||
val: f64,
|
||||
) -> Result<(), ICError> {
|
||||
self.batch_device(source, prefab, None)
|
||||
.map(|device| device.borrow_mut().set_field(typ, val))
|
||||
.map(|device| {
|
||||
self.set_modified(device.borrow().id);
|
||||
device.borrow_mut().set_field(typ, val)
|
||||
})
|
||||
.try_collect()
|
||||
}
|
||||
|
||||
@@ -698,7 +903,10 @@ impl VM {
|
||||
val: f64,
|
||||
) -> Result<(), ICError> {
|
||||
self.batch_device(source, prefab, None)
|
||||
.map(|device| device.borrow_mut().set_slot_field(index, typ, val))
|
||||
.map(|device| {
|
||||
self.set_modified(device.borrow().id);
|
||||
device.borrow_mut().set_slot_field(index, typ, val)
|
||||
})
|
||||
.try_collect()
|
||||
}
|
||||
|
||||
@@ -711,7 +919,10 @@ impl VM {
|
||||
val: f64,
|
||||
) -> Result<(), ICError> {
|
||||
self.batch_device(source, prefab, Some(name))
|
||||
.map(|device| device.borrow_mut().set_field(typ, val))
|
||||
.map(|device| {
|
||||
self.set_modified(device.borrow().id);
|
||||
device.borrow_mut().set_field(typ, val)
|
||||
})
|
||||
.try_collect()
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,19 @@
|
||||
mod utils;
|
||||
mod types;
|
||||
|
||||
use types::{Stack, Registers};
|
||||
use ic10emu::{
|
||||
grammar::{LogicType, SlotLogicType},
|
||||
Connection,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use types::{Registers, Stack};
|
||||
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::{cell::RefCell, rc::Rc, str::FromStr};
|
||||
|
||||
use itertools::Itertools;
|
||||
// use itertools::Itertools;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
fn alert(s: &str);
|
||||
@@ -22,6 +26,16 @@ pub struct DeviceRef {
|
||||
vm: Rc<RefCell<ic10emu::VM>>,
|
||||
}
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, Serialize, Deserialize)]
|
||||
pub enum BindingError {
|
||||
#[error("{0} is not a valid variant")]
|
||||
InvalidEnumVariant(String),
|
||||
#[error("Index {0} is out of range {1}")]
|
||||
OutOfBounds(usize, usize),
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DeviceRef {
|
||||
fn from_device(device: Rc<RefCell<ic10emu::Device>>, vm: Rc<RefCell<ic10emu::VM>>) -> Self {
|
||||
@@ -75,125 +89,81 @@ impl DeviceRef {
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "ip")]
|
||||
pub fn ic_ip(&self) -> Option<u32> {
|
||||
self.device
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().ip)
|
||||
})
|
||||
.flatten()
|
||||
self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().ip)
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "instructionCount")]
|
||||
pub fn ic_instruction_count(&self) -> Option<u16> {
|
||||
self.device
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().ic)
|
||||
})
|
||||
.flatten()
|
||||
self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().ic)
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "stack")]
|
||||
pub fn ic_stack(&self) -> Option<Stack> {
|
||||
self.device
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| Stack(ic.as_ref().borrow().stack))
|
||||
})
|
||||
.flatten()
|
||||
self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| Stack(ic.as_ref().borrow().stack))
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "registers")]
|
||||
pub fn ic_registers(&self) -> Option<Registers> {
|
||||
self.device
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| Registers(ic.as_ref().borrow().registers))
|
||||
})
|
||||
.flatten()
|
||||
self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| Registers(ic.as_ref().borrow().registers))
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "aliases", skip_typescript)]
|
||||
pub fn ic_aliases(&self) -> JsValue {
|
||||
serde_wasm_bindgen::to_value(
|
||||
&self
|
||||
.device
|
||||
serde_wasm_bindgen::to_value(&self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().aliases.clone())
|
||||
})
|
||||
.flatten(),
|
||||
)
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().aliases.clone())
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "defines", skip_typescript)]
|
||||
pub fn ic_defines(&self) -> JsValue {
|
||||
serde_wasm_bindgen::to_value(
|
||||
&self
|
||||
.device
|
||||
serde_wasm_bindgen::to_value(&self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().defines.clone())
|
||||
})
|
||||
.flatten(),
|
||||
)
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().defines.clone())
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "pins", skip_typescript)]
|
||||
pub fn ic_pins(&self) -> JsValue {
|
||||
serde_wasm_bindgen::to_value(
|
||||
&self
|
||||
.device
|
||||
serde_wasm_bindgen::to_value(&self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().pins)
|
||||
})
|
||||
.flatten(),
|
||||
)
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().pins)
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
@@ -203,34 +173,25 @@ impl DeviceRef {
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.borrow().state.clone())
|
||||
})
|
||||
.flatten()
|
||||
.map(|state| state.to_string())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "program")]
|
||||
#[wasm_bindgen(getter, js_name = "program", skip_typescript)]
|
||||
pub fn ic_program(&self) -> JsValue {
|
||||
serde_wasm_bindgen::to_value(
|
||||
&self
|
||||
.device
|
||||
serde_wasm_bindgen::to_value(&self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.borrow().program.clone())
|
||||
})
|
||||
.flatten(),
|
||||
)
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.borrow().program.clone())
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
@@ -284,7 +245,7 @@ impl DeviceRef {
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setStack")]
|
||||
pub fn ic_set_stack(&mut self, address: f64, val: f64) -> Result<f64, JsError> {
|
||||
pub fn ic_set_stack(&self, address: f64, val: f64) -> Result<f64, JsError> {
|
||||
let ic_id = *self
|
||||
.device
|
||||
.borrow()
|
||||
@@ -299,6 +260,77 @@ impl DeviceRef {
|
||||
let result = ic.borrow_mut().poke(address, val)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setName")]
|
||||
pub fn set_name(&self, name: &str) {
|
||||
self.device.borrow_mut().set_name(name)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setField")]
|
||||
pub fn set_field(&self, field: &str, value: f64) -> Result<f64, JsError> {
|
||||
let logic_typ = LogicType::from_str(field)?;
|
||||
let mut device_ref = self.device.borrow_mut();
|
||||
let logic_field = device_ref
|
||||
.fields
|
||||
.get_mut(&logic_typ)
|
||||
.ok_or_else(|| BindingError::InvalidEnumVariant(field.to_owned()))?;
|
||||
let last = logic_field.value;
|
||||
logic_field.value = value;
|
||||
Ok(last)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setSlotField")]
|
||||
pub fn set_slot_field(&self, slot: usize, field: &str, value: f64) -> Result<f64, JsError> {
|
||||
let logic_typ = SlotLogicType::from_str(field)?;
|
||||
let mut device_ref = self.device.borrow_mut();
|
||||
let slots_len = device_ref.slots.len();
|
||||
let slot = device_ref
|
||||
.slots
|
||||
.get_mut(slot)
|
||||
.ok_or(BindingError::OutOfBounds(slot, slots_len))?;
|
||||
let logic_field = slot
|
||||
.fields
|
||||
.get_mut(&logic_typ)
|
||||
.ok_or_else(|| BindingError::InvalidEnumVariant(field.to_owned()))?;
|
||||
let last = logic_field.value;
|
||||
logic_field.value = value;
|
||||
Ok(last)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setConnection")]
|
||||
pub fn set_connection(&self, conn: usize, net: Option<u16>) -> Result<(), JsError> {
|
||||
let mut device_ref = self.device.borrow_mut();
|
||||
let conn_len = device_ref.connections.len();
|
||||
let conn_ref = device_ref
|
||||
.connections
|
||||
.get_mut(conn)
|
||||
.ok_or(BindingError::OutOfBounds(conn, conn_len))?;
|
||||
match conn_ref {
|
||||
&mut Connection::CableNetwork(ref mut net_ref) => *net_ref = net,
|
||||
_ => {
|
||||
*conn_ref = Connection::CableNetwork(net);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "addDeviceToNetwork")]
|
||||
pub fn add_device_to_network(&self, network_id: u16, connection: usize) -> Result<bool, JsError> {
|
||||
let id = self.device.borrow().id;
|
||||
Ok(self.vm.borrow().add_device_to_network(id, network_id, connection)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "removeDeviceFromNetwork")]
|
||||
pub fn remove_device_from_network(&self, network_id: u16) -> Result<bool, JsError> {
|
||||
let id = self.device.borrow().id;
|
||||
Ok(self.vm.borrow().remove_device_from_network(id, network_id)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setPin")]
|
||||
pub fn set_pin(&self, pin: usize, val: Option<u16>) -> Result<bool, JsError> {
|
||||
let id = self.device.borrow().id;
|
||||
Ok(self.vm.borrow().set_pin(id, pin, val)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
@@ -306,6 +338,7 @@ impl DeviceRef {
|
||||
pub struct VM {
|
||||
vm: Rc<RefCell<ic10emu::VM>>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl VM {
|
||||
#[wasm_bindgen(constructor)]
|
||||
@@ -372,6 +405,39 @@ impl VM {
|
||||
pub fn ics(&self) -> Vec<u16> {
|
||||
self.vm.borrow().ics.keys().copied().collect_vec()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "lastOperationModified")]
|
||||
pub fn last_operation_modified(&self) -> Vec<u16> {
|
||||
self.vm.borrow().last_operation_modified()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "visibleDevices")]
|
||||
pub fn visible_devices(&self, source: u16) -> Vec<u16> {
|
||||
self.vm.borrow().visible_devices(source)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "addDeviceToNetwork")]
|
||||
pub fn add_device_to_network(&self, id: u16, network_id: u16, connection: usize) -> Result<bool, JsError> {
|
||||
Ok(self.vm.borrow().add_device_to_network(id, network_id, connection)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "removeDeviceFromNetwork")]
|
||||
pub fn remove_device_from_network(&self, id: u16, network_id: u16) -> Result<bool, JsError> {
|
||||
Ok(self.vm.borrow().remove_device_from_network(id, network_id)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setPin")]
|
||||
pub fn set_pin(&self, id: u16, pin: usize, val: Option<u16>) -> Result<bool, JsError> {
|
||||
Ok(self.vm.borrow().set_pin(id, pin, val)?)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
impl Default for VM {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
||||
@@ -1,36 +1,128 @@
|
||||
type FieldType = 'Read' | 'Write' | 'ReadWrite';
|
||||
export type FieldType = "Read" | "Write" | "ReadWrite";
|
||||
|
||||
interface LogicField {
|
||||
field_type: FieldType,
|
||||
value: number,
|
||||
export interface LogicField {
|
||||
field_type: FieldType;
|
||||
value: number;
|
||||
}
|
||||
type Fields = Map<string, LogicField>;
|
||||
export type Fields = Map<string, LogicField>;
|
||||
|
||||
type SlotType = 'AccessCard' | 'Appliance' | 'Back' | 'Battery' | 'Blocked' | 'Bottle' | 'Cartridge' | 'Circuitboard' | 'CreditCard' | 'DataDisk' | 'DrillHead' | 'Egg' | 'Entity' | 'Flare' | 'GasCanister' | 'GasFilter' | 'Helmet' | 'Ingot' | 'LiquidBottle' | 'LiquidCanister' | 'Magazine' | 'Ore' | 'Organ' | 'Plant' | 'ProgramableChip' | 'ScanningHead' | 'SensorProcessingUnit' | 'SoundCartridge' | 'Suit' | 'Tool' | 'Torpedo' | 'None';
|
||||
|
||||
interface Slot {
|
||||
typ: SlotType,
|
||||
fields: Fields,
|
||||
export type SlotType =
|
||||
| "AccessCard"
|
||||
| "Appliance"
|
||||
| "Back"
|
||||
| "Battery"
|
||||
| "Blocked"
|
||||
| "Bottle"
|
||||
| "Cartridge"
|
||||
| "Circuitboard"
|
||||
| "CreditCard"
|
||||
| "DataDisk"
|
||||
| "DrillHead"
|
||||
| "Egg"
|
||||
| "Entity"
|
||||
| "Flare"
|
||||
| "GasCanister"
|
||||
| "GasFilter"
|
||||
| "Helmet"
|
||||
| "Ingot"
|
||||
| "LiquidBottle"
|
||||
| "LiquidCanister"
|
||||
| "Magazine"
|
||||
| "Ore"
|
||||
| "Organ"
|
||||
| "Plant"
|
||||
| "ProgramableChip"
|
||||
| "ScanningHead"
|
||||
| "SensorProcessingUnit"
|
||||
| "SoundCartridge"
|
||||
| "Suit"
|
||||
| "Tool"
|
||||
| "Torpedo"
|
||||
| "None";
|
||||
|
||||
export interface Slot {
|
||||
typ: SlotType;
|
||||
fields: Fields;
|
||||
}
|
||||
|
||||
type Reagents = Map<string, Map<number, number>>;
|
||||
export type Reagents = Map<string, Map<number, number>>;
|
||||
|
||||
type Connection = { CableNetwork: number } | 'Other' ;
|
||||
export type Connection = { CableNetwork: number } | "Other";
|
||||
|
||||
type Alias = { RegisterSpec: {indirection: number, target: number} } | { DeviceSpec: { device: "Db" | { Numbered: number } | { Indirect: { indirection: number, target: number } } }, connection: number | undefined };
|
||||
export type RegisterSpec = {
|
||||
RegisterSpec: { indirection: number; target: number };
|
||||
};
|
||||
export type DeviceSpec = {
|
||||
DeviceSpec: {
|
||||
device:
|
||||
| "Db"
|
||||
| { Numbered: number }
|
||||
| { Indirect: { indirection: number; target: number } };
|
||||
};
|
||||
connection: number | undefined;
|
||||
};
|
||||
export type LogicType = { LogicType: string };
|
||||
export type SlotLogicType = { SlotLogicType: string };
|
||||
export type BatchMode = { BatchMode: string };
|
||||
export type ReagentMode = { ReagentMode: string };
|
||||
export type Identifier = { Identifier: { name: string } };
|
||||
|
||||
type Aliases = Map<string, Alias>;
|
||||
export type NumberFloat = { Float: number };
|
||||
export type NumberBinary = { Binary: number };
|
||||
export type NumberHexadecimal = { Hexadecimal: number };
|
||||
export type NumberConstant = { Constant: number };
|
||||
export type NumberString = { String: string };
|
||||
export type NumberEnum = { Enum: number };
|
||||
|
||||
type Defines = Map<string, number>;
|
||||
export type NumberOperand = {
|
||||
Number:
|
||||
| NumberFloat
|
||||
| NumberBinary
|
||||
| NumberHexadecimal
|
||||
| NumberConstant
|
||||
| NumberString
|
||||
| NumberEnum;
|
||||
};
|
||||
export type Operand =
|
||||
| RegisterSpec
|
||||
| DeviceSpec
|
||||
| NumberOperand
|
||||
| LogicType
|
||||
| SlotLogicType
|
||||
| BatchMode
|
||||
| ReagentMode
|
||||
| Identifier;
|
||||
|
||||
type Pins = (number | undefined)[]
|
||||
export type Alias = RegisterSpec | DeviceSpec;
|
||||
|
||||
export type Aliases = Map<string, Alias>;
|
||||
|
||||
export type Defines = Map<string, number>;
|
||||
|
||||
export type Pins = (number | undefined)[];
|
||||
|
||||
export interface Instruction {
|
||||
instruction: string;
|
||||
operands: Operand[];
|
||||
}
|
||||
|
||||
export type ICError = {
|
||||
ParseError: { line: number; start: number; end: number; msg: string };
|
||||
};
|
||||
|
||||
export interface Program {
|
||||
instructions: Instruction[];
|
||||
errors: ICError[];
|
||||
labels: Map<string, number>;
|
||||
}
|
||||
|
||||
export interface DeviceRef {
|
||||
readonly fields: Fields;
|
||||
readonly slots: Slot[];
|
||||
readonly reagents: Reagents;
|
||||
readonly connections: Connection[];
|
||||
readonly aliases?: Aliases | undefined;
|
||||
readonly defines?: Defines | undefined;
|
||||
readonly pins?: Pins;
|
||||
readonly fields: Fields;
|
||||
readonly slots: Slot[];
|
||||
readonly reagents: Reagents;
|
||||
readonly connections: Connection[];
|
||||
readonly aliases?: Aliases | undefined;
|
||||
readonly defines?: Defines | undefined;
|
||||
readonly pins?: Pins;
|
||||
readonly program?: Program;
|
||||
}
|
||||
|
||||
4
rust-toolchain.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
profile = "default"
|
||||
targets = [ "wasm32-unknown-unknown" ]
|
||||
317
www/assets_finding.ipynb
Normal file
@@ -0,0 +1,317 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# About\n",
|
||||
"\n",
|
||||
"This is a notebook for finding and copying Textures form extracted game assets to named images for the item database. \n",
|
||||
"\n",
|
||||
"## Why not a script?\n",
|
||||
"\n",
|
||||
"because depending on what extractor you use and the whims of the developers all this could use some serious tweaking every run. The notebook lets you run things in stages and inspect what your working with."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"\n",
|
||||
"database = {}\n",
|
||||
"\n",
|
||||
"with open(\"data/database.json\", \"r\") as f:\n",
|
||||
" database = json.load(f)\n",
|
||||
"\n",
|
||||
"db = database[\"db\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Item Database Pulled in"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from pathlib import Path \n",
|
||||
"\n",
|
||||
"# Location were https://github.com/SeriousCache/UABE has extracted all Texture2D assets\n",
|
||||
"# Change as necessary\n",
|
||||
"datapath = Path(r\"E:\\Games\\SteamLibrary\\steamapps\\common\\Stationeers\\Stationpedia\\exported_textures\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Change this Datapath to point to the extracted textures"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# Pull in a list of all found textures\n",
|
||||
"images = list(datapath.glob(\"*.png\"))\n",
|
||||
"names = [image.name for image in images]\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Finding matches\n",
|
||||
"\n",
|
||||
"This next section loops through all the item names and collects all the candidate textures. Then, through a series of rules, attempts to narrow down the choices to 1 texture."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"image_candidates = {}\n",
|
||||
"\n",
|
||||
"def filter_candidates(candidates):\n",
|
||||
" max_match_len = 0\n",
|
||||
" filtered_matches = []\n",
|
||||
"\n",
|
||||
" # go for longest match\n",
|
||||
" for can in candidates:\n",
|
||||
" name, match, mapping = can\n",
|
||||
" match_len = len(match)\n",
|
||||
" if match_len > max_match_len:\n",
|
||||
" max_match_len = match_len\n",
|
||||
" filtered_matches = [(name, match, mapping)]\n",
|
||||
" elif match_len == max_match_len:\n",
|
||||
" filtered_matches.append((name, match, mapping))\n",
|
||||
"\n",
|
||||
" # choose better matches\n",
|
||||
" if len(filtered_matches) > 1:\n",
|
||||
" better_matches = []\n",
|
||||
" for can in filtered_matches:\n",
|
||||
" name, match, mapping = can\n",
|
||||
" if mapping.startswith(\"Item\") and mapping in name:\n",
|
||||
" better_matches.append((name, match, mapping))\n",
|
||||
" elif mapping.startswith(\"Structure\") and mapping in name:\n",
|
||||
" better_matches.append((name, match, mapping))\n",
|
||||
" if len(better_matches) > 0:\n",
|
||||
" filtered_matches = better_matches\n",
|
||||
"\n",
|
||||
" #exclude build states if we have non build states\n",
|
||||
" if len(filtered_matches) > 1:\n",
|
||||
" non_build_state = []\n",
|
||||
" for can in filtered_matches:\n",
|
||||
" name, match, mapping = can\n",
|
||||
" if \"BuildState\" not in name:\n",
|
||||
" non_build_state.append((name, match, mapping))\n",
|
||||
" if len(non_build_state) > 0:\n",
|
||||
" filtered_matches = non_build_state\n",
|
||||
"\n",
|
||||
" #prefer matches without extra tags\n",
|
||||
" if len(filtered_matches) > 1:\n",
|
||||
" direct = []\n",
|
||||
" for can in filtered_matches:\n",
|
||||
" name, match, mapping = can\n",
|
||||
" if f\"{match}-\" in name:\n",
|
||||
" direct.append((name, match, mapping))\n",
|
||||
" if len(direct) > 0:\n",
|
||||
" filtered_matches = direct\n",
|
||||
" \n",
|
||||
" #filter to unique filenames\n",
|
||||
" if len(filtered_matches) > 1:\n",
|
||||
" unique_names = []\n",
|
||||
" unique_matches = []\n",
|
||||
" for can in filtered_matches:\n",
|
||||
" name, match, mapping = can\n",
|
||||
" if name not in unique_names:\n",
|
||||
" unique_names.append(name)\n",
|
||||
" unique_matches.append((name, match, mapping))\n",
|
||||
" filtered_matches = unique_matches\n",
|
||||
"\n",
|
||||
" #prefer not worse matches\n",
|
||||
" if len(filtered_matches) > 1:\n",
|
||||
" not_worse = []\n",
|
||||
" for can in filtered_matches:\n",
|
||||
" name, match, mapping = can\n",
|
||||
" if name.startswith(\"Item\") and not mapping.startswith(\"Item\"):\n",
|
||||
" continue\n",
|
||||
" elif name.startswith(\"Structure\") and not mapping.startswith(\"Structure\"):\n",
|
||||
" continue\n",
|
||||
" elif name.startswith(\"Kit\") and not mapping.startswith(\"Kit\"):\n",
|
||||
" continue\n",
|
||||
" elif not name.startswith(match):\n",
|
||||
" continue\n",
|
||||
" not_worse.append((name, match, mapping))\n",
|
||||
" if len(not_worse) > 0:\n",
|
||||
" filtered_matches = not_worse\n",
|
||||
"\n",
|
||||
" #if we have colored variants take White\n",
|
||||
" if len(filtered_matches) > 1:\n",
|
||||
" for can in filtered_matches:\n",
|
||||
" name, match, mapping = can\n",
|
||||
" if f\"_White\" in name:\n",
|
||||
" return [name]\n",
|
||||
"\n",
|
||||
" return [name for name, _, _ in filtered_matches]\n",
|
||||
"\n",
|
||||
"for entry in db.values():\n",
|
||||
" candidates = []\n",
|
||||
" for name in names:\n",
|
||||
" if entry[\"name\"] in name:\n",
|
||||
" candidates.append((name, entry[\"name\"], entry[\"name\"]))\n",
|
||||
" if entry[\"name\"].removeprefix(\"Item\") in name:\n",
|
||||
" candidates.append((name, entry[\"name\"].removeprefix(\"Item\"), entry[\"name\"]))\n",
|
||||
" if entry[\"name\"].removeprefix(\"Structure\") in name:\n",
|
||||
" candidates.append((name, entry[\"name\"].removeprefix(\"Structure\"), entry[\"name\"]))\n",
|
||||
" image_candidates[entry[\"name\"]] = filter_candidates(candidates)\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Some Items end up with no match but these items are often subtypes of an item that will have a match"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# rematch items to super structure?\n",
|
||||
"for name in image_candidates.keys():\n",
|
||||
" for other in image_candidates.keys():\n",
|
||||
" if name != other and name in other:\n",
|
||||
" if len(image_candidates[name]) > 0 and len(image_candidates[other]) == 0:\n",
|
||||
" image_candidates[other] = image_candidates[name]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Prepare out List of file copies. at this point a few items will never have a match. and one or two will have two choices but those choices will be arbitrary."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"ItemBiomass []\n",
|
||||
"StructureBlocker []\n",
|
||||
"CartridgePlantAnalyser []\n",
|
||||
"StructureElevatorLevelIndustrial []\n",
|
||||
"ItemPlantEndothermic_Creative []\n",
|
||||
"Flag_ODA_10m []\n",
|
||||
"Flag_ODA_4m []\n",
|
||||
"Flag_ODA_6m []\n",
|
||||
"Flag_ODA_8m []\n",
|
||||
"ItemHorticultureBelt []\n",
|
||||
"ItemKitLiquidRegulator []\n",
|
||||
"ItemKitPortablesConnector []\n",
|
||||
"Landingpad_GasConnectorInwardPiece []\n",
|
||||
"Landingpad_LiquidConnectorInwardPiece []\n",
|
||||
"ItemMushroom ['ItemMushroom-resources.assets-3022.png', 'ItemMushroom-resources.assets-9304.png']\n",
|
||||
"StructurePlinth []\n",
|
||||
"ItemPlantThermogenic_Creative []\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"to_copy = []\n",
|
||||
"for name, candidates in image_candidates.items():\n",
|
||||
" if len(candidates) != 1:\n",
|
||||
" print(name, candidates)\n",
|
||||
" if len(candidates) > 1:\n",
|
||||
" #take first as fallback\n",
|
||||
" to_copy.append((name, candidates[0]))\n",
|
||||
" else:\n",
|
||||
" # print(name, candidates)\n",
|
||||
" to_copy.append((name, candidates[0]))\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"1223 of 1223 | 100.00% \n",
|
||||
"Done\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import shutil\n",
|
||||
"\n",
|
||||
"destpath = Path(\"img/stationpedia\")\n",
|
||||
"total_files = len(to_copy)\n",
|
||||
"\n",
|
||||
"count = 0\n",
|
||||
"print ( f\"{count} of {total_files} | { count / total_files * 100}\", end=\"\\r\")\n",
|
||||
"for name, file in to_copy:\n",
|
||||
" source = datapath / file\n",
|
||||
" dest = destpath / f\"{name}.png\"\n",
|
||||
" shutil.copy(source, dest)\n",
|
||||
" count += 1\n",
|
||||
" print ( f\"{count} of {total_files} | { (count / total_files) * 100 :.2f}% \", end=\"\\r\")\n",
|
||||
"print()\n",
|
||||
"print(\"Done\")\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
BIN
www/img/stationpedia/DynamicGasTankAdvancedOxygen.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 11 KiB |