From fb192fbbe64c459fea8c7cfe772dc930d118ace8 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 13 Apr 2024 21:44:59 -0700 Subject: [PATCH] link error field, enhance networks, improve device update envents --- ic10emu/src/interpreter.rs | 2 +- ic10emu/src/lib.rs | 265 ++++++++++++++++++--------- ic10emu_wasm/src/lib.rs | 42 +---- ic10emu_wasm/src/types.ts | 32 ++-- www/src/ts/session.ts | 7 +- www/src/ts/virtual_machine/device.ts | 37 ++-- www/src/ts/virtual_machine/index.ts | 35 ++-- 7 files changed, 236 insertions(+), 184 deletions(-) diff --git a/ic10emu/src/interpreter.rs b/ic10emu/src/interpreter.rs index d19681e..e73ee1c 100644 --- a/ic10emu/src/interpreter.rs +++ b/ic10emu/src/interpreter.rs @@ -107,7 +107,7 @@ pub enum ICError { #[error("connection specifier missing")] MissingConnectionSpecifier, #[error("no data network on connection '{0}'")] - NotDataConnection(usize), + NotACableConnection(usize), #[error("network not connected on connection '{0}'")] NetworkNotConnected(usize), #[error("bad network Id '{0}'")] diff --git a/ic10emu/src/lib.rs b/ic10emu/src/lib.rs index afbfd8e..9346dd0 100644 --- a/ic10emu/src/lib.rs +++ b/ic10emu/src/lib.rs @@ -16,6 +16,8 @@ use itertools::Itertools; use serde::{Deserialize, Serialize}; use thiserror::Error; +use crate::interpreter::ICState; + #[derive(Error, Debug, Serialize, Deserialize)] pub enum VMError { #[error("device with id '{0}' does not exist")] @@ -226,9 +228,20 @@ impl Slot { } } +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] +pub enum CableConnectionType { + Power, + Data, + #[default] + PowerAndData, +} + #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] pub enum Connection { - CableNetwork(Option), + CableNetwork { + net: Option, + typ: CableConnectionType, + }, #[default] Other, } @@ -282,9 +295,18 @@ impl Connection { | ConnectionType::LandingPad | ConnectionType::LaunchPad | ConnectionType::PipeLiquid => Self::Other, - ConnectionType::Data | ConnectionType::Power | ConnectionType::PowerAndData => { - Self::CableNetwork(None) - } + ConnectionType::Data => Self::CableNetwork { + net: None, + typ: CableConnectionType::Data, + }, + ConnectionType::Power => Self::CableNetwork { + net: None, + typ: CableConnectionType::Power, + }, + ConnectionType::PowerAndData => Self::CableNetwork { + net: None, + typ: CableConnectionType::PowerAndData, + }, } } } @@ -352,6 +374,7 @@ pub struct Device { pub reagents: HashMap>, pub ic: Option, pub connections: Vec, + pub on: bool, fields: HashMap, } @@ -467,7 +490,11 @@ impl Device { slots: Vec::new(), reagents: HashMap::new(), ic: None, - connections: vec![Connection::CableNetwork(None)], + on: true, + connections: vec![Connection::CableNetwork { + net: None, + typ: CableConnectionType::default(), + }], }; device.fields.insert( LogicType::ReferenceId, @@ -482,24 +509,19 @@ impl Device { pub fn with_ic(id: u32, ic: u32) -> Self { let mut device = Device::new(id); device.ic = Some(ic); - device.connections = vec![Connection::CableNetwork(None), Connection::Other]; + device.connections = vec![ + Connection::CableNetwork { + net: None, + typ: CableConnectionType::Data, + }, + Connection::CableNetwork { + net: None, + typ: CableConnectionType::Power, + }, + ]; device.prefab_name = Some("StructureCircuitHousing".to_owned()); device.prefab_hash = Some(-128473777); 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 { @@ -507,13 +529,6 @@ impl Device { value: 0.0, }, ), - ( - LogicType::On, - LogicField { - field_type: FieldType::ReadWrite, - value: 0.0, - }, - ), ( LogicType::RequiredPower, LogicField { @@ -528,20 +543,6 @@ impl Device { 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::with_occupant( SlotType::ProgramableChip, @@ -563,7 +564,35 @@ impl Device { value: ic.ip as f64, }, ); + copy.insert( + LogicType::Error, + LogicField { + field_type: FieldType::Read, + value: match ic.state { + ICState::Error(_) => 1.0, + _ => 0.0, + }, + }, + ); } + copy.insert( + LogicType::On, + LogicField { + field_type: FieldType::ReadWrite, + value: if self.on && self.has_power() { + 1.0 + } else { + 0.0 + }, + }, + ); + copy.insert( + LogicType::Power, + LogicField { + field_type: FieldType::Read, + value: if self.has_power() { 1.0 } else { 0.0 }, + }, + ); copy } @@ -573,14 +602,17 @@ impl Device { connection, self.connections.len(), )) - } else if let Connection::CableNetwork(network_id) = self.connections[connection] { + } else if let Connection::CableNetwork { + net: network_id, .. + } = self.connections[connection] + { if let Some(network_id) = network_id { Ok(network_id) } else { Err(ICError::NetworkNotConnected(connection)) } } else { - Err(ICError::NotDataConnection(connection)) + Err(ICError::NotACableConnection(connection)) } } @@ -598,7 +630,7 @@ impl Device { // when reading it's own IC Ok(0.0) } - } else if let Some(field) = self.fields.get(&typ) { + } else if let Some(field) = self.get_fields(vm).get(&typ) { if field.field_type == FieldType::Read || field.field_type == FieldType::ReadWrite { Ok(field.value) } else { @@ -610,7 +642,10 @@ impl Device { } pub fn set_field(&mut self, typ: LogicType, val: f64, vm: &VM) -> Result<(), ICError> { - if typ == LogicType::LineNumber && self.ic.is_some() { + if typ == LogicType::On { + self.on = val != 0.0; + Ok(()) + } else if typ == LogicType::LineNumber && self.ic.is_some() { // try borrow to set ip, we shoudl only fail if the ic is in us aka is is *our* ic // in game trying to set your own ic's LineNumber appears to be a Nop so this is fine. if let Ok(mut ic) = vm @@ -754,6 +789,20 @@ impl Device { self.name_hash = Some((const_crc32::crc32(name.as_bytes()) as i32).into()); self.name = Some(name.to_owned()); } + + pub fn has_power(&self) -> bool { + self.connections.iter().any(|conn| { + if let Connection::CableNetwork { net, typ } = conn { + net.is_some() + && matches!( + typ, + CableConnectionType::Power | CableConnectionType::PowerAndData + ) + } else { + false + } + }) + } } impl Default for VM { @@ -809,8 +858,12 @@ impl VM { } let mut device = self.new_device(); if let Some(first_network) = device.connections.iter_mut().find_map(|c| { - if let Connection::CableNetwork(c) = c { - Some(c) + if let Connection::CableNetwork { + net, + typ: CableConnectionType::Data | CableConnectionType::PowerAndData, + } = c + { + Some(net) } else { None } @@ -828,19 +881,22 @@ impl VM { .iter() .enumerate() .find_map(|(index, conn)| match conn { - Connection::CableNetwork(_) => Some(index), - Connection::Other => None, + Connection::CableNetwork { + typ: CableConnectionType::Data | CableConnectionType::PowerAndData, + .. + } => Some(index), + _ => None, }); self.devices.insert(id, Rc::new(RefCell::new(device))); if let Some(first_data_network) = first_data_network { - let _ = self.add_device_to_network( + let _ = self.set_device_connection( id, - if let Some(network) = network { - network - } else { - self.default_network - }, first_data_network, + if let Some(network) = network { + Some(network) + } else { + Some(self.default_network) + }, ); } Ok(id) @@ -854,8 +910,12 @@ impl VM { } let (mut device, ic) = self.new_ic(); if let Some(first_network) = device.connections.iter_mut().find_map(|c| { - if let Connection::CableNetwork(c) = c { - Some(c) + if let Connection::CableNetwork { + net, + typ: CableConnectionType::Data | CableConnectionType::PowerAndData, + } = c + { + Some(net) } else { None } @@ -873,20 +933,23 @@ impl VM { .iter() .enumerate() .find_map(|(index, conn)| match conn { - Connection::CableNetwork(_) => Some(index), - Connection::Other => None, + Connection::CableNetwork { + typ: CableConnectionType::Data | CableConnectionType::PowerAndData, + .. + } => Some(index), + _ => None, }); self.devices.insert(id, Rc::new(RefCell::new(device))); self.ics.insert(ic_id, Rc::new(RefCell::new(ic))); if let Some(first_data_network) = first_data_network { - let _ = self.add_device_to_network( + let _ = self.set_device_connection( id, - if let Some(network) = network { - network - } else { - self.default_network - }, first_data_network, + if let Some(network) = network { + Some(network) + } else { + Some(self.default_network) + }, ); } Ok(id) @@ -1120,32 +1183,56 @@ impl VM { } } - pub fn add_device_to_network( + pub fn set_device_connection( &self, id: u32, - network_id: u32, connection: usize, + target_net: Option, ) -> Result { - 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 { - Err(VMError::InvalidNetwork(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()); } + + { + // scope this borrow + let connections = &device.borrow().connections; + let Connection::CableNetwork { net, .. } = & connections[connection] else { + return Err(ICError::NotACableConnection(connection).into()); + }; + // remove from current network + if let Some(net) = net { + if let Some(network) = self.networks.get(net) { + // if there is no other connection to this network + if connections.clone().iter().enumerate().all(|(index, conn)| { + if let Connection::CableNetwork { net: other_net, .. } = conn { + !(other_net.is_some_and(|id| id == *net) && index != connection) + } else { + true + } + }) { + network.borrow_mut().remove(id); + } + } + } + } + let mut device_ref = device.borrow_mut(); + let connections = &mut device_ref.connections; + let Connection::CableNetwork { ref mut net, .. } = connections[connection] else { + return Err(ICError::NotACableConnection(connection).into()); + }; + if let Some(target_net) = target_net { + if let Some(network) = self.networks.get(&target_net) { + network.borrow_mut().add(id); + } else { + return Err(VMError::InvalidNetwork(target_net)); + } + } + *net = target_net; + Ok(true) } pub fn remove_device_from_network(&self, id: u32, network_id: u32) -> Result { @@ -1156,9 +1243,9 @@ impl VM { 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; + if let Connection::CableNetwork { net, .. } = conn { + if net.is_some_and(|id| id == network_id) { + *net = None; } } } diff --git a/ic10emu_wasm/src/lib.rs b/ic10emu_wasm/src/lib.rs index 9e0ecb2..569a226 100644 --- a/ic10emu_wasm/src/lib.rs +++ b/ic10emu_wasm/src/lib.rs @@ -2,10 +2,7 @@ mod utils; mod types; -use ic10emu::{ - grammar::{LogicType, SlotLogicType}, - Connection, -}; +use ic10emu::grammar::{LogicType, SlotLogicType}; use serde::{Deserialize, Serialize}; use types::{Registers, Stack}; @@ -311,32 +308,11 @@ impl DeviceRef { #[wasm_bindgen(js_name = "setConnection")] pub fn set_connection(&self, conn: usize, net: Option) -> 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: u32, - connection: usize, - ) -> Result { - let id = self.device.borrow().id; - Ok(self - .vm + let device_id = self.device.borrow().id; + self.vm .borrow() - .add_device_to_network(id, network_id, connection)?) + .set_device_connection(device_id, conn, net)?; + Ok(()) } #[wasm_bindgen(js_name = "removeDeviceFromNetwork")] @@ -438,17 +414,17 @@ impl VM { self.vm.borrow().visible_devices(source) } - #[wasm_bindgen(js_name = "addDeviceToNetwork")] - pub fn add_device_to_network( + #[wasm_bindgen(js_name = "setDeviceConnection")] + pub fn set_device_connection( &self, id: u32, - network_id: u32, connection: usize, + network_id: Option, ) -> Result { Ok(self .vm .borrow() - .add_device_to_network(id, network_id, connection)?) + .set_device_connection(id, connection, network_id)?) } #[wasm_bindgen(js_name = "removeDeviceFromNetwork")] diff --git a/ic10emu_wasm/src/types.ts b/ic10emu_wasm/src/types.ts index 707c25a..a38f3ce 100644 --- a/ic10emu_wasm/src/types.ts +++ b/ic10emu_wasm/src/types.ts @@ -56,7 +56,9 @@ export interface Slot { export type Reagents = Map>; -export type Connection = { CableNetwork: number } | "Other"; +export type Connection = + | { readonly CableNetwork: { readonly net: number; readonly typ: string } } + | "Other"; export type RegisterSpec = { readonly RegisterSpec: { @@ -67,14 +69,14 @@ export type RegisterSpec = { export type DeviceSpec = { readonly DeviceSpec: { readonly device: - | "Db" - | { readonly Numbered: number } - | { - readonly Indirect: { - readonly indirection: number; - readonly target: number; - }; - }; + | "Db" + | { readonly Numbered: number } + | { + readonly Indirect: { + readonly indirection: number; + readonly target: number; + }; + }; }; readonly connection: number | undefined; }; @@ -93,12 +95,12 @@ export type NumberEnum = { readonly Enum: number }; export type NumberOperand = { Number: - | NumberFloat - | NumberBinary - | NumberHexadecimal - | NumberConstant - | NumberString - | NumberEnum; + | NumberFloat + | NumberBinary + | NumberHexadecimal + | NumberConstant + | NumberString + | NumberEnum; }; export type Operand = | RegisterSpec diff --git a/www/src/ts/session.ts b/www/src/ts/session.ts index b34a445..97e0706 100644 --- a/www/src/ts/session.ts +++ b/www/src/ts/session.ts @@ -117,8 +117,11 @@ export class Session extends EventTarget { } setActiveLine(id: number, line: number) { - this._activeLines.set(id, line); - this._fireOnActiveLine(id); + const last = this._activeLines.get(id); + if (last !== line) { + this._activeLines.set(id, line); + this._fireOnActiveLine(id); + } } set activeLine(line: number) { diff --git a/www/src/ts/virtual_machine/device.ts b/www/src/ts/virtual_machine/device.ts index b2fc437..fe92585 100644 --- a/www/src/ts/virtual_machine/device.ts +++ b/www/src/ts/virtual_machine/device.ts @@ -24,7 +24,6 @@ import "@shoelace-style/shoelace/dist/components/icon/icon.js"; import SlInput from "@shoelace-style/shoelace/dist/components/input/input.js"; import { parseNumber, structuralEqual } from "../utils"; import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.js"; -import SlDetails from "@shoelace-style/shoelace/dist/components/details/details.js"; import SlDrawer from "@shoelace-style/shoelace/dist/components/drawer/drawer.js"; import { DeviceDB, DeviceDBEntry } from "./device_db"; @@ -250,15 +249,16 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { placement="top" clearable key=${index} - value=${conn} + value=${conn?.net} ?disabled=${conn === null} @sl-change=${this._handleChangeConnection} > - Connection:${index} + Connection:${index} ${vmNetworks.map( (net) => html`Network ${net}`, )} + ${conn?.typ} `; })} @@ -340,23 +340,8 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { _handleChangeConnection(e: CustomEvent) { const select = e.target as SlSelect; const conn = parseInt(select.getAttribute("key")!); - const last = this.device.connections[conn]; const val = select.value ? parseInt(select.value as string) : undefined; - if (typeof last === "object" && typeof last.CableNetwork === "number") { - // is there no other connection to the previous network? - if ( - !this.device.connections.some((other_conn, index) => { - structuralEqual(last, other_conn) && index !== conn; - }) - ) { - this.device.removeDeviceFromNetwork(last.CableNetwork); - } - } - if (typeof val !== "undefined") { - this.device.addDeviceToNetwork(conn, val); - } else { - this.device.setConnection(conn, val); - } + this.device.setConnection(conn, val); this.updateDevice(); } @@ -378,7 +363,7 @@ export class VMDeviceList extends BaseElement { ...defaultCss, css` .header { - margin-botton: 1rem; + margin-bottom: 1rem; margin-right: 2rem; padding: 0.25rem 0.25rem; align-items: center; @@ -686,11 +671,11 @@ export class VmDeviceTemplate extends BaseElement { } renderSlot(slot: Slot, slotIndex: number): HTMLTemplateResult { - return html` `; + return html` `; } renderSlots(): HTMLTemplateResult { - return html`
`; + return html`
`; } renderReagents(): HTMLTemplateResult { @@ -699,12 +684,12 @@ export class VmDeviceTemplate extends BaseElement { renderNetworks(): HTMLTemplateResult { const vmNetworks = window.VM!.networks; - return html`
`; + return html`
`; } renderPins(): HTMLTemplateResult { const device = this.deviceDB.db[this.name]; - return html`
`; + return html`
`; } render() { @@ -724,8 +709,8 @@ export class VmDeviceTemplate extends BaseElement { ${device.hash} Add + >Add +
diff --git a/www/src/ts/virtual_machine/index.ts b/www/src/ts/virtual_machine/index.ts index bdfc220..e98e807 100644 --- a/www/src/ts/virtual_machine/index.ts +++ b/www/src/ts/virtual_machine/index.ts @@ -1,5 +1,5 @@ import { DeviceRef, VM, init } from "ic10emu_wasm"; -import { DeviceDB } from "./device_db" +import { DeviceDB } from "./device_db"; import "./base_device"; declare global { @@ -8,14 +8,13 @@ declare global { } } - class VirtualMachine extends EventTarget { ic10vm: VM; _devices: Map; _ics: Map; accessor db: DeviceDB; - dbPromise: Promise<{ default: DeviceDB }> + dbPromise: Promise<{ default: DeviceDB }>; constructor() { super(); @@ -29,7 +28,7 @@ class VirtualMachine extends EventTarget { this._ics = new Map(); this.dbPromise = import("../../../data/database.json"); - this.dbPromise.then((module) => this.setupDeviceDatabase(module.default)) + this.dbPromise.then((module) => this.setupDeviceDatabase(module.default)); this.updateDevices(); this.updateCode(); @@ -179,17 +178,23 @@ class VirtualMachine extends EventTarget { ); } }, this); - const ic = this.activeIC!; - window.App!.session.setActiveLine(window.App!.session.activeIC, ic.ip!); + this.updateDevice(this.activeIC) + } + + updateDevice(device: DeviceRef) { + this.dispatchEvent( + new CustomEvent("vm-device-modified", { detail: device.id }), + ); + if (typeof device.ic !== "undefined") { + window.App!.session.setActiveLine(device.id, device.ip!); + } } setRegister(index: number, val: number) { const ic = this.activeIC!; try { ic.setRegister(index, val); - this.dispatchEvent( - new CustomEvent("vm-device-modified", { detail: ic.id }), - ); + this.updateDevice(ic); } catch (e) { console.log(e); } @@ -199,9 +204,7 @@ class VirtualMachine extends EventTarget { const ic = this.activeIC!; try { ic!.setStack(addr, val); - this.dispatchEvent( - new CustomEvent("vm-device-modified", { detail: ic.id }), - ); + this.updateDevice(ic); } catch (e) { console.log(e); } @@ -222,9 +225,7 @@ class VirtualMachine extends EventTarget { if (device) { try { device.setField(field, val); - this.dispatchEvent( - new CustomEvent("vm-device-modified", { detail: id }), - ); + this.updateDevice(device); return true; } catch (e) { console.log(e); @@ -238,9 +239,7 @@ class VirtualMachine extends EventTarget { if (device) { try { device.setSlotField(slot, field, val); - this.dispatchEvent( - new CustomEvent("vm-device-modified", { detail: id }), - ); + this.updateDevice(device); return true; } catch (e) { console.log(e);