link error field, enhance networks, improve device update envents

This commit is contained in:
Rachel Powers
2024-04-13 21:44:59 -07:00
parent d50eabe594
commit fb192fbbe6
7 changed files with 236 additions and 184 deletions

View File

@@ -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}'")]

View File

@@ -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<u32>),
CableNetwork {
net: Option<u32>,
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<ReagentMode, HashMap<i32, f64>>,
pub ic: Option<u32>,
pub connections: Vec<Connection>,
pub on: bool,
fields: HashMap<LogicType, LogicField>,
}
@@ -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<u32>,
) -> 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 {
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<bool, VMError> {
@@ -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;
}
}
}

View File

@@ -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<u32>) -> 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<bool, JsError> {
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<u32>,
) -> Result<bool, JsError> {
Ok(self
.vm
.borrow()
.add_device_to_network(id, network_id, connection)?)
.set_device_connection(id, connection, network_id)?)
}
#[wasm_bindgen(js_name = "removeDeviceFromNetwork")]

View File

@@ -56,7 +56,9 @@ export interface Slot {
export type Reagents = Map<string, Map<number, number>>;
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

View File

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

View File

@@ -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}
>
<span slot="prefix">Connection:${index}</span>
<span slot="prefix">Connection:${index} </span>
${vmNetworks.map(
(net) =>
html`<sl-option value=${net}>Network ${net}</sl-option>`,
)}
<span slot="prefix"> ${conn?.typ} </span>
</sl-select>
`;
})}
@@ -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` <sl-card class="slot-card"> </sl-card> `;
return html`<sl-card class="slot-card"> </sl-card>`;
}
renderSlots(): HTMLTemplateResult {
return html` <div clas="slots"></div> `;
return html`<div clas="slots"></div>`;
}
renderReagents(): HTMLTemplateResult {
@@ -699,12 +684,12 @@ export class VmDeviceTemplate extends BaseElement {
renderNetworks(): HTMLTemplateResult {
const vmNetworks = window.VM!.networks;
return html` <div class="networks"></div> `;
return html`<div class="networks"></div>`;
}
renderPins(): HTMLTemplateResult {
const device = this.deviceDB.db[this.name];
return html` <div class="pins"></div> `;
return html`<div class="pins"></div>`;
}
render() {
@@ -724,8 +709,8 @@ export class VmDeviceTemplate extends BaseElement {
<span class="prefab-hash">${device.hash}</span>
</div>
<sl-button class="ms-auto" pill variant="success"
>Add <sl-icon slot="prefix" name="plus-lg"></sl-icon
></sl-button>
>Add <sl-icon slot="prefix" name="plus-lg"></sl-icon>
</sl-button>
</div>
<div class="card-body">
<sl-tab-group>

View File

@@ -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<number, DeviceRef>;
_ics: Map<number, DeviceRef>;
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);