save VM state
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -571,6 +571,7 @@ dependencies = [
|
||||
"rand",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_with",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"thiserror",
|
||||
|
||||
@@ -16,6 +16,7 @@ phf = "0.11.2"
|
||||
rand = "0.8.5"
|
||||
regex = "1.10.3"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde_with = "3.7.0"
|
||||
strum = { version = "0.26.2", features = ["derive", "phf", "strum_macros"] }
|
||||
strum_macros = "0.26.2"
|
||||
thiserror = "1.0.58"
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
use crate::{
|
||||
grammar::{LogicType, ReagentMode, SlotLogicType}, interpreter::{ICError, ICState}, network::{CableConnectionType, Connection}, vm::VM
|
||||
grammar::{LogicType, ReagentMode, SlotLogicType},
|
||||
interpreter::{ICError, ICState},
|
||||
network::{CableConnectionType, Connection},
|
||||
vm::VM,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, ops::Deref};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::{AsRefStr, EnumIter, EnumString};
|
||||
@@ -486,7 +491,6 @@ pub enum SlotType {
|
||||
None = 0,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct Prefab {
|
||||
pub name: String,
|
||||
@@ -512,18 +516,7 @@ pub struct Device {
|
||||
pub reagents: HashMap<ReagentMode, HashMap<i32, f64>>,
|
||||
pub ic: Option<u32>,
|
||||
pub connections: Vec<Connection>,
|
||||
pub fields: HashMap<LogicType, LogicField>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct DeviceTemplate {
|
||||
pub id: Option<u32>,
|
||||
pub name: Option<String>,
|
||||
pub prefab_name: Option<String>,
|
||||
pub slots: Vec<SlotTemplate>,
|
||||
// pub reagents: HashMap<ReagentMode, HashMap<i32, f64>>,
|
||||
pub connections: Vec<Connection>,
|
||||
pub fields: HashMap<LogicType, LogicField>,
|
||||
fields: HashMap<LogicType, LogicField>,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
@@ -925,3 +918,94 @@ impl Device {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct DeviceTemplate {
|
||||
pub id: Option<u32>,
|
||||
pub name: Option<String>,
|
||||
pub prefab_name: Option<String>,
|
||||
pub slots: Vec<SlotTemplate>,
|
||||
// pub reagents: HashMap<ReagentMode, HashMap<i32, f64>>,
|
||||
pub connections: Vec<Connection>,
|
||||
pub fields: HashMap<LogicType, LogicField>,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
/// create a devive from a template and return the device, does not create it's own IC
|
||||
pub fn from_template<F>(template: DeviceTemplate, mut id_fn: F) -> Self
|
||||
where
|
||||
F: FnMut() -> u32,
|
||||
{
|
||||
// id_fn *must* be captured not moved
|
||||
#[allow(clippy::redundant_closure)]
|
||||
let device_id = template.id.unwrap_or_else(|| id_fn());
|
||||
let name_hash = template
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|name| const_crc32::crc32(name.as_bytes()) as i32);
|
||||
|
||||
#[allow(clippy::redundant_closure)]
|
||||
let slots = template
|
||||
.slots
|
||||
.into_iter()
|
||||
.map(|slot| Slot {
|
||||
typ: slot.typ,
|
||||
occupant: slot
|
||||
.occupant
|
||||
.map(|occupant| SlotOccupant::from_template(occupant, || id_fn())),
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
let ic = slots
|
||||
.iter()
|
||||
.find_map(|slot| {
|
||||
if slot.typ == SlotType::ProgrammableChip && slot.occupant.is_some() {
|
||||
Some(slot.occupant.clone()).flatten()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|occupant| occupant.id);
|
||||
|
||||
let fields = template.fields;
|
||||
|
||||
Device {
|
||||
id: device_id,
|
||||
name: template.name,
|
||||
name_hash,
|
||||
prefab: template.prefab_name.map(|name| Prefab::new(&name)),
|
||||
slots,
|
||||
// reagents: template.reagents,
|
||||
reagents: HashMap::new(),
|
||||
ic,
|
||||
connections: template.connections,
|
||||
fields,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for DeviceTemplate
|
||||
where
|
||||
T: Deref<Target = Device>,
|
||||
{
|
||||
fn from(device: T) -> Self {
|
||||
DeviceTemplate {
|
||||
id: Some(device.id),
|
||||
name: device.name.clone(),
|
||||
prefab_name: device.prefab.as_ref().map(|prefab| prefab.name.clone()),
|
||||
slots: device
|
||||
.slots
|
||||
.iter()
|
||||
.map(|slot| SlotTemplate {
|
||||
typ: slot.typ,
|
||||
occupant: slot.occupant.as_ref().map(|occupant| SlotOccupantTemplate {
|
||||
id: Some(occupant.id),
|
||||
fields: occupant.get_fields(),
|
||||
}),
|
||||
})
|
||||
.collect_vec(),
|
||||
connections: device.connections.clone(),
|
||||
fields: device.fields.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use core::f64;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::string::ToString;
|
||||
use std::{ops::Deref, string::ToString};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
error::Error,
|
||||
@@ -12,7 +12,12 @@ use itertools::Itertools;
|
||||
|
||||
use time::format_description;
|
||||
|
||||
use crate::{grammar::{self, ParseError}, vm::VM};
|
||||
use crate::{
|
||||
grammar::{self, ParseError},
|
||||
vm::VM,
|
||||
};
|
||||
|
||||
use serde_with::serde_as;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -113,7 +118,7 @@ pub enum ICError {
|
||||
#[error("channel index out of range '{0}'")]
|
||||
ChannelIndexOutOfRange(usize),
|
||||
#[error("slot has no occupant")]
|
||||
SlotNotOccupied
|
||||
SlotNotOccupied,
|
||||
}
|
||||
|
||||
impl ICError {
|
||||
@@ -189,6 +194,65 @@ pub struct IC {
|
||||
pub state: ICState,
|
||||
}
|
||||
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FrozenIC {
|
||||
pub device: u32,
|
||||
pub id: u32,
|
||||
pub registers: [f64; 18],
|
||||
/// Instruction Pointer
|
||||
pub ip: u32,
|
||||
/// Instruction Count since last yield
|
||||
pub ic: u16,
|
||||
#[serde_as(as = "[_; 512]")]
|
||||
pub stack: [f64; 512],
|
||||
pub aliases: HashMap<String, grammar::Operand>,
|
||||
pub defines: HashMap<String, f64>,
|
||||
pub pins: [Option<u32>; 6],
|
||||
pub state: ICState,
|
||||
pub code: String,
|
||||
}
|
||||
|
||||
impl<T> From<T> for FrozenIC
|
||||
where T: Deref<Target = IC>
|
||||
{
|
||||
fn from(ic: T) -> Self {
|
||||
FrozenIC {
|
||||
device: ic.device,
|
||||
id: ic.id,
|
||||
registers: ic.registers,
|
||||
ip: ic.ip,
|
||||
ic: ic.ic,
|
||||
stack: ic.stack,
|
||||
aliases: ic.aliases.clone(),
|
||||
defines: ic.defines.clone(),
|
||||
pins: ic.pins,
|
||||
state: ic.state.clone(),
|
||||
code: ic.code.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FrozenIC> for IC {
|
||||
fn from(value: FrozenIC) -> Self {
|
||||
IC {
|
||||
device: value.device,
|
||||
id: value.id,
|
||||
registers: value.registers,
|
||||
ip: value.ip,
|
||||
ic: value.ic,
|
||||
stack: value.stack,
|
||||
aliases: value.aliases,
|
||||
defines: value.defines,
|
||||
pins: value.pins,
|
||||
state: value.state,
|
||||
code: value.code.clone(),
|
||||
program: Program::from_code_with_invalid(&value.code),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Program {
|
||||
pub instructions: Vec<grammar::Instruction>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::HashSet;
|
||||
use std::{collections::HashSet, ops::Deref};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::{AsRefStr, EnumIter};
|
||||
@@ -85,17 +85,41 @@ impl Connection {
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Network {
|
||||
pub id: u32,
|
||||
pub devices: HashSet<u32>,
|
||||
pub power_only: HashSet<u32>,
|
||||
pub channels: [f64; 8],
|
||||
}
|
||||
|
||||
impl Default for Network {
|
||||
fn default() -> Self {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FrozenNetwork {
|
||||
pub id: u32,
|
||||
pub devices: Vec<u32>,
|
||||
pub power_only: Vec<u32>,
|
||||
pub channels: [f64; 8],
|
||||
}
|
||||
|
||||
impl<T> From<T> for FrozenNetwork
|
||||
where
|
||||
T: Deref<Target = Network>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
FrozenNetwork {
|
||||
id: value.id,
|
||||
devices: value.devices.iter().copied().collect_vec(),
|
||||
power_only: value.power_only.iter().copied().collect_vec(),
|
||||
channels: value.channels,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FrozenNetwork> for Network {
|
||||
fn from(value: FrozenNetwork) -> Self {
|
||||
Network {
|
||||
devices: HashSet::new(),
|
||||
power_only: HashSet::new(),
|
||||
channels: [f64::NAN; 8],
|
||||
id: value.id,
|
||||
devices: value.devices.into_iter().collect(),
|
||||
power_only: value.power_only.into_iter().collect(),
|
||||
channels: value.channels,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,6 +131,16 @@ pub enum NetworkError {
|
||||
}
|
||||
|
||||
impl Network {
|
||||
|
||||
pub fn new(id: u32) -> Self {
|
||||
Network {
|
||||
id,
|
||||
devices: HashSet::new(),
|
||||
power_only: HashSet::new(),
|
||||
channels: [f64::NAN; 8],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains(&self, id: &u32) -> bool {
|
||||
self.devices.contains(id) || self.power_only.contains(id)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
use crate::{
|
||||
device::{Device, DeviceTemplate},
|
||||
grammar::{BatchMode, LogicType, SlotLogicType},
|
||||
interpreter::{self, ICError, LineError},
|
||||
device::{Device, DeviceTemplate, Prefab, Slot, SlotOccupant, SlotType},
|
||||
network::{CableConnectionType, Connection, Network},
|
||||
interpreter::{self, FrozenIC, ICError, LineError},
|
||||
network::{CableConnectionType, Connection, FrozenNetwork, Network},
|
||||
};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::{HashMap, HashSet},
|
||||
rc::Rc,
|
||||
};
|
||||
use std::{cell::RefCell, collections::{HashMap, HashSet}, rc::Rc};
|
||||
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -41,7 +45,7 @@ pub struct VM {
|
||||
pub networks: HashMap<u32, Rc<RefCell<Network>>>,
|
||||
pub default_network: u32,
|
||||
id_space: IdSpace,
|
||||
network_id_gen: IdSpace,
|
||||
network_id_space: IdSpace,
|
||||
random: Rc<RefCell<crate::rand_mscorlib::Random>>,
|
||||
|
||||
/// list of device id's touched on the last operation
|
||||
@@ -58,9 +62,9 @@ impl VM {
|
||||
pub fn new() -> Self {
|
||||
let id_gen = IdSpace::default();
|
||||
let mut network_id_space = IdSpace::default();
|
||||
let default_network = Rc::new(RefCell::new(Network::default()));
|
||||
let mut networks = HashMap::new();
|
||||
let default_network_key = network_id_space.next();
|
||||
let default_network = Rc::new(RefCell::new(Network::new(default_network_key)));
|
||||
let mut networks = HashMap::new();
|
||||
networks.insert(default_network_key, default_network);
|
||||
|
||||
let mut vm = VM {
|
||||
@@ -69,7 +73,7 @@ impl VM {
|
||||
networks,
|
||||
default_network: default_network_key,
|
||||
id_space: id_gen,
|
||||
network_id_gen: network_id_space,
|
||||
network_id_space,
|
||||
random: Rc::new(RefCell::new(crate::rand_mscorlib::Random::new())),
|
||||
operation_modified: RefCell::new(Vec::new()),
|
||||
};
|
||||
@@ -208,70 +212,24 @@ impl VM {
|
||||
}
|
||||
|
||||
// collect the id's this template wants to use
|
||||
let mut to_use_ids = template
|
||||
let to_use_ids = template
|
||||
.slots
|
||||
.iter()
|
||||
.filter_map(|slot| slot.occupant.as_ref().and_then(|occupant| occupant.id))
|
||||
.collect_vec();
|
||||
let device_id = {
|
||||
// attempt to use all the idea at once to error without needing to clean up.
|
||||
if let Some(id) = &template.id {
|
||||
to_use_ids.push(*id);
|
||||
self.id_space.use_ids(&to_use_ids)?;
|
||||
*id
|
||||
} else {
|
||||
self.id_space.use_ids(&to_use_ids)?;
|
||||
self.id_space.next()
|
||||
}
|
||||
};
|
||||
|
||||
let name_hash = template
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|name| const_crc32::crc32(name.as_bytes()) as i32);
|
||||
// use those ids or fail
|
||||
self.id_space.use_ids(&to_use_ids)?;
|
||||
|
||||
let slots = template
|
||||
.slots
|
||||
.into_iter()
|
||||
.map(|slot| Slot {
|
||||
typ: slot.typ,
|
||||
occupant: slot
|
||||
.occupant
|
||||
.map(|occupant| SlotOccupant::from_template(occupant, || self.id_space.next())),
|
||||
})
|
||||
.collect_vec();
|
||||
let device = Device::from_template(template, || self.id_space.next());
|
||||
let device_id: u32 = device.id;
|
||||
|
||||
let ic = slots
|
||||
.iter()
|
||||
.find_map(|slot| {
|
||||
if slot.typ == SlotType::ProgrammableChip && slot.occupant.is_some() {
|
||||
Some(slot.occupant.clone()).flatten()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|occupant| occupant.id);
|
||||
|
||||
if let Some(ic_id) = &ic {
|
||||
// if this device says it has an IC make it so.
|
||||
if let Some(ic_id) = &device.ic {
|
||||
let chip = interpreter::IC::new(*ic_id, device_id);
|
||||
self.ics.insert(*ic_id, Rc::new(RefCell::new(chip)));
|
||||
}
|
||||
|
||||
let fields = template.fields;
|
||||
|
||||
let device = Device {
|
||||
id: device_id,
|
||||
name: template.name,
|
||||
name_hash,
|
||||
prefab: template.prefab_name.map(|name| Prefab::new(&name)),
|
||||
slots,
|
||||
// reagents: template.reagents,
|
||||
reagents: HashMap::new(),
|
||||
ic,
|
||||
connections: template.connections,
|
||||
fields,
|
||||
};
|
||||
|
||||
device.connections.iter().for_each(|conn| {
|
||||
if let Connection::CableNetwork {
|
||||
net: Some(net),
|
||||
@@ -298,9 +256,9 @@ impl VM {
|
||||
}
|
||||
|
||||
pub fn add_network(&mut self) -> u32 {
|
||||
let next_id = self.network_id_gen.next();
|
||||
let next_id = self.network_id_space.next();
|
||||
self.networks
|
||||
.insert(next_id, Rc::new(RefCell::new(Network::default())));
|
||||
.insert(next_id, Rc::new(RefCell::new(Network::new(next_id))));
|
||||
next_id
|
||||
}
|
||||
|
||||
@@ -470,7 +428,7 @@ impl VM {
|
||||
.filter(move |(id, device)| {
|
||||
device
|
||||
.borrow()
|
||||
.fields
|
||||
.get_fields(self)
|
||||
.get(&LogicType::PrefabHash)
|
||||
.is_some_and(|f| f.value == prefab_hash)
|
||||
&& (name.is_none()
|
||||
@@ -790,6 +748,81 @@ impl VM {
|
||||
self.id_space.free_id(id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save_vm_state(&self) -> FrozenVM {
|
||||
FrozenVM {
|
||||
ics: self.ics.values().map(|ic| ic.borrow().into()).collect(),
|
||||
devices: self
|
||||
.devices
|
||||
.values()
|
||||
.map(|device| device.borrow().into())
|
||||
.collect(),
|
||||
networks: self
|
||||
.networks
|
||||
.values()
|
||||
.map(|network| network.borrow().into())
|
||||
.collect(),
|
||||
default_network: self.default_network,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restore_vm_state(&mut self, state: FrozenVM) -> Result<(), VMError> {
|
||||
self.ics.clear();
|
||||
self.devices.clear();
|
||||
self.networks.clear();
|
||||
self.id_space.reset();
|
||||
self.network_id_space.reset();
|
||||
|
||||
// ic ids sould be in slot occupants, don't duplicate
|
||||
let to_use_ids = state
|
||||
.devices
|
||||
.iter()
|
||||
.map(|template| {
|
||||
let mut ids = template
|
||||
.slots
|
||||
.iter()
|
||||
.filter_map(|slot| slot.occupant.as_ref().and_then(|occupant| occupant.id))
|
||||
.collect_vec();
|
||||
if let Some(id) = template.id {
|
||||
ids.push(id);
|
||||
}
|
||||
ids
|
||||
})
|
||||
.concat();
|
||||
self.id_space.use_ids(&to_use_ids)?;
|
||||
|
||||
self.network_id_space
|
||||
.use_ids(&state.networks.iter().map(|net| net.id).collect_vec())?;
|
||||
|
||||
self.ics = state
|
||||
.ics
|
||||
.into_iter()
|
||||
.map(|ic| (ic.id, Rc::new(RefCell::new(ic.into()))))
|
||||
.collect();
|
||||
self.devices = state
|
||||
.devices
|
||||
.into_iter()
|
||||
.map(|template| {
|
||||
let device = Device::from_template(template, || self.id_space.next());
|
||||
(device.id, Rc::new(RefCell::new(device)))
|
||||
})
|
||||
.collect();
|
||||
self.networks = state
|
||||
.networks
|
||||
.into_iter()
|
||||
.map(|network| (network.id, Rc::new(RefCell::new(network.into()))))
|
||||
.collect();
|
||||
self.default_network = state.default_network;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FrozenVM {
|
||||
pub ics: Vec<FrozenIC>,
|
||||
pub devices: Vec<DeviceTemplate>,
|
||||
pub networks: Vec<FrozenNetwork>,
|
||||
pub default_network: u32,
|
||||
}
|
||||
|
||||
impl BatchMode {
|
||||
@@ -877,4 +910,8 @@ impl IdSpace {
|
||||
pub fn free_id(&mut self, id: u32) {
|
||||
self.in_use.remove(&id);
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.in_use.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ mod utils;
|
||||
mod types;
|
||||
|
||||
use ic10emu::{
|
||||
grammar::{LogicType, SlotLogicType},
|
||||
device::{Device, DeviceTemplate},
|
||||
vm::{VMError, VM},
|
||||
grammar::{LogicType, SlotLogicType},
|
||||
vm::{FrozenVM, VMError, VM},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use types::{Registers, Stack};
|
||||
@@ -217,6 +217,17 @@ impl DeviceRef {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "code")]
|
||||
pub fn get_code(&self) -> Option<String> {
|
||||
self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.borrow().code.clone())
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "step")]
|
||||
pub fn step_ic(&self, advance_ip_on_err: bool) -> Result<bool, JsError> {
|
||||
let id = self.device.borrow().id;
|
||||
@@ -297,7 +308,13 @@ impl DeviceRef {
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setSlotField", skip_typescript)]
|
||||
pub fn set_slot_field(&self, slot: f64, field: &str, value: f64, force: bool) -> Result<(), JsError> {
|
||||
pub fn set_slot_field(
|
||||
&self,
|
||||
slot: f64,
|
||||
field: &str,
|
||||
value: f64,
|
||||
force: bool,
|
||||
) -> Result<(), JsError> {
|
||||
let logic_typ = SlotLogicType::from_str(field)?;
|
||||
let mut device_ref = self.device.borrow_mut();
|
||||
device_ref.set_slot_field(slot, logic_typ, value, &self.vm.borrow(), force)?;
|
||||
@@ -471,6 +488,19 @@ impl VMRef {
|
||||
pub fn remove_device(&self, id: u32) -> Result<(), JsError> {
|
||||
Ok(self.vm.borrow_mut().remove_device(id)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "saveVMState", skip_typescript)]
|
||||
pub fn save_vm_state(&self) -> JsValue {
|
||||
let state = self.vm.borrow().save_vm_state();
|
||||
serde_wasm_bindgen::to_value(&state).unwrap()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "restoreVMState", skip_typescript)]
|
||||
pub fn restore_vm_state(&self, state: JsValue) -> Result<(), JsError> {
|
||||
let state: FrozenVM = serde_wasm_bindgen::from_value(state)?;
|
||||
self.vm.borrow_mut().restore_vm_state(state)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VMRef {
|
||||
|
||||
@@ -147,6 +147,36 @@ export interface DeviceTemplate {
|
||||
fields: { [key in LogicType]?: LogicField };
|
||||
}
|
||||
|
||||
export interface FrozenIC {
|
||||
device: number;
|
||||
id: number;
|
||||
registers: number[];
|
||||
ip: number;
|
||||
ic: number[];
|
||||
stack: number[];
|
||||
aliases: Aliases;
|
||||
defines: Defines;
|
||||
pins: Pins;
|
||||
state: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface FrozenNetwork {
|
||||
id: number;
|
||||
devices: number[];
|
||||
power_only: number[];
|
||||
channels: number[];
|
||||
}
|
||||
|
||||
export interface FrozenVM {
|
||||
ics: FrozenIC[];
|
||||
devices: DeviceTemplate[];
|
||||
networks: FrozenNetwork[];
|
||||
default_network: number;
|
||||
}
|
||||
|
||||
export interface VMRef {
|
||||
addDeviceFromTemplate(template: DeviceTemplate): number;
|
||||
saveVMState(): FrozenVM;
|
||||
restoreVMState(state: FrozenVM): void;
|
||||
}
|
||||
|
||||
@@ -52,14 +52,14 @@ export class App extends BaseElement {
|
||||
// return this.renderRoot.querySelector("ace-ic10") as IC10Editor;
|
||||
// }
|
||||
|
||||
vm!: VirtualMachine;
|
||||
session!: Session;
|
||||
vm: VirtualMachine;
|
||||
session: Session;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
window.App = this;
|
||||
this.session = new Session();
|
||||
this.vm = new VirtualMachine();
|
||||
this.session = new Session(this);
|
||||
this.vm = new VirtualMachine(this);
|
||||
window.App.set(this);
|
||||
}
|
||||
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment {
|
||||
@@ -81,7 +81,7 @@ export class App extends BaseElement {
|
||||
snap="512px 50%"
|
||||
snap-threshold="15"
|
||||
>
|
||||
<ace-ic10 slot="start" style=""></ace-ic10>
|
||||
<ace-ic10 slot="start"></ace-ic10>
|
||||
<div slot="end"><vm-ui></vm-ui></div>
|
||||
</sl-split-panel>
|
||||
</div>
|
||||
@@ -107,8 +107,4 @@ export class App extends BaseElement {
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
App?: App;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -268,11 +268,12 @@ export class IC10Editor extends BaseElement {
|
||||
this.initializeEditor();
|
||||
}
|
||||
|
||||
initializeEditor() {
|
||||
async initializeEditor() {
|
||||
let editor = this.editor;
|
||||
const that = this;
|
||||
|
||||
window.App!.session.onLoad(((e: CustomEvent) => {
|
||||
const app = await window.App.get();
|
||||
app.session.onLoad(((e: CustomEvent) => {
|
||||
const session = e.detail;
|
||||
const updated_ids: number[] = [];
|
||||
for (const [id, _] of session.programs) {
|
||||
@@ -286,10 +287,10 @@ export class IC10Editor extends BaseElement {
|
||||
}
|
||||
}
|
||||
}) as EventListener);
|
||||
window.App!.session.loadFromFragment();
|
||||
app.session.loadFromFragment();
|
||||
|
||||
window.App!.session.onActiveLine(((e: CustomEvent) => {
|
||||
const session = window.App?.session!;
|
||||
app.session.onActiveLine(((e: CustomEvent) => {
|
||||
const session = app.session;
|
||||
const id: number = e.detail;
|
||||
const active_line = session.getActiveLine(id);
|
||||
if (typeof active_line !== "undefined") {
|
||||
@@ -587,7 +588,7 @@ export class IC10Editor extends BaseElement {
|
||||
if (session) {
|
||||
session.on("change", () => {
|
||||
var val = session.getValue();
|
||||
window.App?.session.setProgramCode(session_id, val);
|
||||
window.App.get().then(app => app.session.setProgramCode(session_id, val));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import { IC10Editor } from "./editor";
|
||||
import { Session } from "./session";
|
||||
import { VirtualMachine } from "./virtual_machine";
|
||||
import { docReady, openFile, saveFile } from "./utils";
|
||||
// import { makeRequest } from "./utils";
|
||||
|
||||
// const dbPromise = makeRequest({ method: "GET", url: "/data/database.json"});
|
||||
// const dbPromise = fetch("/data/database.json").then(resp => resp.json());
|
||||
|
||||
// docReady(() => {
|
||||
// App.vm = new VirtualMachine();
|
||||
//
|
||||
// dbPromise.then((db) => App.vm.setupDeviceDatabase(db));
|
||||
//
|
||||
// const init_session_id = App.vm.devices.get(0).id;
|
||||
//
|
||||
// // App.editor = new IC10Editor(init_session_id);
|
||||
//
|
||||
// // setupLspWorker().then((worker) => {
|
||||
// // App.editor.setupLsp(worker);
|
||||
// // });
|
||||
//
|
||||
// // Menu
|
||||
// document.getElementById("mainMenuShare").addEventListener(
|
||||
// "click",
|
||||
// (_event) => {
|
||||
// const link = document.getElementById("shareLinkText") as HTMLInputElement;
|
||||
// link.setAttribute("value", window.location.href);
|
||||
// link.setSelectionRange(0, 0);
|
||||
// },
|
||||
// { capture: true },
|
||||
// );
|
||||
// document.getElementById("shareLinkCopyButton").addEventListener(
|
||||
// "click",
|
||||
// (event) => {
|
||||
// event.preventDefault();
|
||||
// const link = document.getElementById("shareLinkText") as HTMLInputElement;
|
||||
// link.select();
|
||||
// link.setSelectionRange(0, 99999);
|
||||
// navigator.clipboard.writeText(link.value);
|
||||
// },
|
||||
// { capture: true },
|
||||
// );
|
||||
// document.getElementById("mainMenuOpenFile").addEventListener(
|
||||
// "click",
|
||||
// (_event) => {
|
||||
// openFile(App.editor.editor);
|
||||
// },
|
||||
// { capture: true },
|
||||
// );
|
||||
// document.getElementById("mainMenuSaveAs").addEventListener(
|
||||
// "click",
|
||||
// (_event) => {
|
||||
// saveFile(App.editor.editor.getSession().getValue());
|
||||
// },
|
||||
// { capture: true },
|
||||
// );
|
||||
// document.getElementById("mainMenuKeyboardShortcuts").addEventListener(
|
||||
// "click",
|
||||
// (_event) => {
|
||||
// App.editor.editor.execCommand("showKeyboardShortcuts");
|
||||
// },
|
||||
// { capture: true },
|
||||
// );
|
||||
// });
|
||||
@@ -1,10 +1,79 @@
|
||||
import "@popperjs/core";
|
||||
import "../scss/styles.scss";
|
||||
import { Dropdown, Modal } from "bootstrap";
|
||||
import "./app";
|
||||
|
||||
// A dependency graph that contains any wasm must all be imported
|
||||
// asynchronously. This `main.js` file does the single async import, so
|
||||
// that no one else needs to worry about it again.
|
||||
// import("./index")
|
||||
// .catch(e => console.error("Error importing `index.ts`:", e));
|
||||
class DeferedApp {
|
||||
|
||||
app: App;
|
||||
private resolvers: ((value: App) => void)[];
|
||||
|
||||
constructor() {
|
||||
this.app = undefined;
|
||||
this.resolvers = [];
|
||||
}
|
||||
|
||||
get(): Promise<App> {
|
||||
const that = this;
|
||||
return new Promise(resolve => {
|
||||
if (typeof that.app !== "undefined") {
|
||||
that.resolvers.push(resolve);
|
||||
} else {
|
||||
resolve(that.app);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
set(app: App) {
|
||||
this.app = app;
|
||||
while(this.resolvers.length) {
|
||||
this.resolvers.shift()(this.app);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DeferedVM {
|
||||
|
||||
vm: VirtualMachine;
|
||||
private resolvers: ((value: VirtualMachine) => void)[];
|
||||
|
||||
constructor() {
|
||||
this.vm = undefined;
|
||||
this.resolvers = [];
|
||||
}
|
||||
|
||||
get(): Promise<VirtualMachine> {
|
||||
const that = this;
|
||||
return new Promise(resolve => {
|
||||
if (typeof that.vm !== "undefined") {
|
||||
that.resolvers.push(resolve);
|
||||
} else {
|
||||
resolve(that.vm);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
set(vm: VirtualMachine) {
|
||||
this.vm = vm;
|
||||
while(this.resolvers.length) {
|
||||
this.resolvers.shift()(this.vm);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window
|
||||
{
|
||||
App: DeferedApp;
|
||||
VM: DeferedVM;
|
||||
}
|
||||
}
|
||||
|
||||
window.App = new DeferedApp();
|
||||
window.VM = new DeferedVM();
|
||||
|
||||
import type { App } from "./app";
|
||||
import type { VirtualMachine } from "./virtual_machine";
|
||||
|
||||
import("./app");
|
||||
|
||||
@@ -60,22 +60,28 @@ j ra
|
||||
|
||||
`;
|
||||
|
||||
import type { ICError } from "ic10emu_wasm";
|
||||
import type { ICError, FrozenVM } from "ic10emu_wasm";
|
||||
import { App } from "./app";
|
||||
|
||||
export class Session extends EventTarget {
|
||||
_programs: Map<number, string>;
|
||||
_errors: Map<number, ICError[]>;
|
||||
_activeIC: number;
|
||||
_activeLines: Map<number, number>;
|
||||
_activeLine: number;
|
||||
_save_timeout?: ReturnType<typeof setTimeout>;
|
||||
constructor() {
|
||||
private _programs: Map<number, string>;
|
||||
private _errors: Map<number, ICError[]>;
|
||||
private _activeIC: number;
|
||||
private _activeLines: Map<number, number>;
|
||||
private _save_timeout?: ReturnType<typeof setTimeout>;
|
||||
private _vm_state: FrozenVM;
|
||||
|
||||
private app: App;
|
||||
|
||||
constructor(app: App) {
|
||||
super();
|
||||
this.app = app;
|
||||
this._programs = new Map();
|
||||
this._errors = new Map();
|
||||
this._save_timeout = undefined;
|
||||
this._activeIC = 1;
|
||||
this._activeLines = new Map();
|
||||
this._vm_state = undefined;
|
||||
this.loadFromFragment();
|
||||
|
||||
const that = this;
|
||||
@@ -84,11 +90,11 @@ export class Session extends EventTarget {
|
||||
});
|
||||
}
|
||||
|
||||
get programs() {
|
||||
get programs(): Map<number, string> {
|
||||
return this._programs;
|
||||
}
|
||||
|
||||
set programs(programs) {
|
||||
set programs(programs: Iterable<[number, string]>) {
|
||||
this._programs = new Map([...programs]);
|
||||
this._fireOnLoad();
|
||||
}
|
||||
@@ -124,10 +130,6 @@ export class Session extends EventTarget {
|
||||
}
|
||||
}
|
||||
|
||||
set activeLine(line: number) {
|
||||
this._activeLine = line;
|
||||
}
|
||||
|
||||
setProgramCode(id: number, code: string) {
|
||||
this._programs.set(id, code);
|
||||
this.save();
|
||||
@@ -178,18 +180,18 @@ export class Session extends EventTarget {
|
||||
if (this._save_timeout) clearTimeout(this._save_timeout);
|
||||
this._save_timeout = setTimeout(() => {
|
||||
this.saveToFragment();
|
||||
if (window.App!.vm) {
|
||||
window.App!.vm.updateCode();
|
||||
if (this.app.vm) {
|
||||
this.app.vm.updateCode();
|
||||
}
|
||||
this._save_timeout = undefined;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async saveToFragment() {
|
||||
const toSave = { programs: Array.from(this._programs) };
|
||||
const toSave = { vmState: this.app.vm.saveVMState(), activeIC: this.activeIC };
|
||||
const bytes = new TextEncoder().encode(JSON.stringify(toSave));
|
||||
try {
|
||||
const c_bytes = await compress(bytes);
|
||||
const c_bytes = await compress(bytes, defaultCompression);
|
||||
const fragment = base64url_encode(c_bytes);
|
||||
window.history.replaceState(null, "", `#${fragment}`);
|
||||
} catch (e) {
|
||||
@@ -216,21 +218,77 @@ export class Session extends EventTarget {
|
||||
this._programs = new Map([[1, txt]]);
|
||||
this, this._fireOnLoad();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this._programs = new Map(data.programs);
|
||||
this._fireOnLoad();
|
||||
return;
|
||||
} catch (e) {
|
||||
console.log("Bad session data:", e);
|
||||
} else if ("programs" in data) {
|
||||
try {
|
||||
this._programs = new Map(data.programs);
|
||||
this._fireOnLoad();
|
||||
return;
|
||||
} catch (e) {
|
||||
console.log("Bad session data:", e);
|
||||
}
|
||||
} else if ("vmState" in data && "activeIC" in data) {
|
||||
try {
|
||||
this._programs = new Map();
|
||||
const state = data.vmState as FrozenVM;
|
||||
// assign first so it's present when the
|
||||
// vm setting the programs list fires events
|
||||
this._activeIC = data.activeIC;
|
||||
this.app.vm.restoreVMState(state);
|
||||
this.programs = this.app.vm.getPrograms();
|
||||
// assign again to fire event
|
||||
this.activeIC = data.activeIC;
|
||||
this._fireOnLoad();
|
||||
return;
|
||||
} catch (e) {
|
||||
console.log("Bad session data:", e);
|
||||
}
|
||||
} else {
|
||||
console.log("Bad session data:", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const byteToHex: string[] = [];
|
||||
|
||||
for (let n = 0; n <= 0xff; ++n) {
|
||||
const hexOctet = n.toString(16).padStart(2, "0");
|
||||
byteToHex.push(hexOctet);
|
||||
}
|
||||
|
||||
function bufToHex(arrayBuffer: ArrayBuffer): string {
|
||||
const buff = new Uint8Array(arrayBuffer);
|
||||
const hexOctets = new Array(buff.length);
|
||||
|
||||
for (let i = 0; i < buff.length; ++i) hexOctets[i] = byteToHex[buff[i]];
|
||||
|
||||
return hexOctets.join("");
|
||||
}
|
||||
|
||||
export type CompressionFormat = "gzip" | "deflate" | "deflate-raw";
|
||||
const defaultCompression = "gzip";
|
||||
|
||||
function guessFormat(bytes: ArrayBuffer): CompressionFormat {
|
||||
const header = bufToHex(bytes.slice(0, 8));
|
||||
if (
|
||||
header.startsWith("789c") ||
|
||||
header.startsWith("7801") ||
|
||||
header.startsWith("78DA")
|
||||
) {
|
||||
return "deflate";
|
||||
} else if (header.startsWith("1f8b08")) {
|
||||
return "gzip";
|
||||
} else {
|
||||
return "deflate-raw";
|
||||
}
|
||||
}
|
||||
|
||||
async function decompressFragment(c_bytes: ArrayBuffer) {
|
||||
try {
|
||||
const bytes = await decompress(c_bytes);
|
||||
const format = guessFormat(c_bytes);
|
||||
console.log("Decompressing fragment with:", format);
|
||||
const bytes = await decompress(c_bytes, format);
|
||||
return bytes;
|
||||
} catch (e) {
|
||||
console.log("Error decompressing content fragment:", e);
|
||||
@@ -290,9 +348,12 @@ async function concatUintArrays(arrays: Uint8Array[]) {
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
async function compress(bytes: ArrayBuffer) {
|
||||
async function compress(
|
||||
bytes: ArrayBuffer,
|
||||
format: CompressionFormat = defaultCompression,
|
||||
) {
|
||||
const s = new Blob([bytes]).stream();
|
||||
const cs = s.pipeThrough(new CompressionStream("deflate-raw"));
|
||||
const cs = s.pipeThrough(new CompressionStream(format));
|
||||
const chunks: Uint8Array[] = [];
|
||||
for await (const chunk of streamAsyncIterator(cs)) {
|
||||
chunks.push(chunk);
|
||||
@@ -300,9 +361,12 @@ async function compress(bytes: ArrayBuffer) {
|
||||
return await concatUintArrays(chunks);
|
||||
}
|
||||
|
||||
async function decompress(bytes: ArrayBuffer) {
|
||||
async function decompress(
|
||||
bytes: ArrayBuffer,
|
||||
format: CompressionFormat = defaultCompression,
|
||||
) {
|
||||
const s = new Blob([bytes]).stream();
|
||||
const ds = s.pipeThrough(new DecompressionStream("deflate-raw"));
|
||||
const ds = s.pipeThrough(new DecompressionStream(format));
|
||||
const chunks: Uint8Array[] = [];
|
||||
for await (const chunk of streamAsyncIterator(ds)) {
|
||||
chunks.push(chunk);
|
||||
|
||||
@@ -19,6 +19,11 @@ function replacer(key: any, value: any) {
|
||||
dataType: 'Map',
|
||||
value: Array.from(value.entries()), // or with spread: value: [...value]
|
||||
};
|
||||
} else if (Number.isNaN(value)) {
|
||||
return {
|
||||
dataType: 'Number',
|
||||
value: "NaN",
|
||||
}
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
@@ -28,6 +33,8 @@ function reviver(_key: any, value: any) {
|
||||
if(typeof value === 'object' && value !== null) {
|
||||
if (value.dataType === 'Map') {
|
||||
return new Map(value.value);
|
||||
} else if (value.dataType === 'Number') {
|
||||
return parseFloat(value.value)
|
||||
}
|
||||
}
|
||||
return value;
|
||||
|
||||
@@ -79,14 +79,14 @@ export const VMDeviceMixin = <T extends Constructor<LitElement>>(
|
||||
|
||||
connectedCallback(): void {
|
||||
const root = super.connectedCallback();
|
||||
window.VM?.addEventListener(
|
||||
window.VM.get().then(vm => vm.addEventListener(
|
||||
"vm-device-modified",
|
||||
this._handleDeviceModified.bind(this),
|
||||
);
|
||||
window.VM?.addEventListener(
|
||||
));
|
||||
window.VM.get().then(vm => vm.addEventListener(
|
||||
"vm-devices-update",
|
||||
this._handleDevicesModified.bind(this),
|
||||
);
|
||||
));
|
||||
this.updateDevice();
|
||||
return root;
|
||||
}
|
||||
@@ -106,7 +106,7 @@ export const VMDeviceMixin = <T extends Constructor<LitElement>>(
|
||||
}
|
||||
|
||||
updateDevice() {
|
||||
this.device = window.VM!.devices.get(this.deviceID)!;
|
||||
this.device = window.VM.vm.devices.get(this.deviceID)!;
|
||||
|
||||
const name = this.device.name ?? null;
|
||||
if (this.name !== name) {
|
||||
@@ -189,16 +189,16 @@ export const VMActiveICMixin = <T extends Constructor<LitElement>>(
|
||||
class VMActiveICMixinClass extends VMDeviceMixin(superClass) {
|
||||
constructor() {
|
||||
super();
|
||||
this.deviceID = window.App!.session.activeIC;
|
||||
this.deviceID = window.App.app.session.activeIC;
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
const root = super.connectedCallback();
|
||||
window.VM?.addEventListener(
|
||||
window.VM.get().then(vm => vm.addEventListener(
|
||||
"vm-run-ic",
|
||||
this._handleDeviceModified.bind(this),
|
||||
);
|
||||
window.App?.session.addEventListener(
|
||||
));
|
||||
window.App.app.session.addEventListener(
|
||||
"session-active-ic",
|
||||
this._handleActiveIC.bind(this),
|
||||
);
|
||||
@@ -209,7 +209,7 @@ export const VMActiveICMixin = <T extends Constructor<LitElement>>(
|
||||
const id = e.detail;
|
||||
if (this.deviceID !== id) {
|
||||
this.deviceID = id;
|
||||
this.device = window.VM!.devices.get(this.deviceID)!;
|
||||
this.device = window.VM.vm.devices.get(this.deviceID)!;
|
||||
}
|
||||
this.updateDevice();
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ export class VMICControls extends VMActiveICMixin(BaseElement) {
|
||||
@query(".active-ic-select") accessor activeICSelect: SlSelect;
|
||||
|
||||
protected render() {
|
||||
const ics = Array.from(window.VM!.ics);
|
||||
const ics = Array.from(window.VM.vm.ics);
|
||||
return html`
|
||||
<sl-card class="card">
|
||||
<div class="controls" slot="header">
|
||||
@@ -170,13 +170,13 @@ export class VMICControls extends VMActiveICMixin(BaseElement) {
|
||||
}
|
||||
|
||||
_handleRunClick() {
|
||||
window.VM?.run();
|
||||
window.VM.get().then(vm => vm.run());
|
||||
}
|
||||
_handleStepClick() {
|
||||
window.VM?.step();
|
||||
window.VM.get().then(vm => vm.step());
|
||||
}
|
||||
_handleResetClick() {
|
||||
window.VM?.reset();
|
||||
window.VM.get().then(vm => vm.reset());
|
||||
}
|
||||
|
||||
updateIC(): void {
|
||||
@@ -194,6 +194,6 @@ export class VMICControls extends VMActiveICMixin(BaseElement) {
|
||||
_handleChangeActiveIC(e: CustomEvent) {
|
||||
const select = e.target as SlSelect;
|
||||
const icId = parseInt(select.value as string);
|
||||
window.App!.session.activeIC = icId;
|
||||
window.App.app.session.activeIC = icId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import SlDrawer from "@shoelace-style/shoelace/dist/components/drawer/drawer.js"
|
||||
import { DeviceDB, DeviceDBEntry } from "./device_db";
|
||||
import { connectionFromDeviceDBConnection } from "./utils";
|
||||
import { SlDialog } from "@shoelace-style/shoelace";
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
|
||||
@customElement("vm-device-card")
|
||||
export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
|
||||
@@ -166,7 +167,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
window.VM!.addEventListener(
|
||||
window.VM.vm.addEventListener(
|
||||
"vm-device-db-loaded",
|
||||
this._handleDeviceDBLoad.bind(this),
|
||||
);
|
||||
@@ -182,7 +183,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
|
||||
}
|
||||
|
||||
renderHeader(): HTMLTemplateResult {
|
||||
const activeIc = window.VM?.activeIC;
|
||||
const activeIc = window.VM.vm.activeIC;
|
||||
const thisIsActiveIc = activeIc.id === this.deviceID;
|
||||
const badges: HTMLTemplateResult[] = [];
|
||||
if (this.deviceID == activeIc?.id) {
|
||||
@@ -197,30 +198,67 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
|
||||
}, this);
|
||||
return html`
|
||||
<sl-tooltip content="${this.prefabName}">
|
||||
<img class="image" src="img/stationpedia/${this.prefabName}.png"
|
||||
onerror="this.src = '${VMDeviceCard.transparentImg}'" />
|
||||
<img
|
||||
class="image"
|
||||
src="img/stationpedia/${this.prefabName}.png"
|
||||
onerror="this.src = '${VMDeviceCard.transparentImg}'"
|
||||
/>
|
||||
</sl-tooltip>
|
||||
<div class="header-name">
|
||||
<sl-input id="vmDeviceCard${this.deviceID}Id" class="device-id" size="small" pill value=${this.deviceID}
|
||||
@sl-change=${this._handleChangeID}>
|
||||
<sl-input
|
||||
id="vmDeviceCard${this.deviceID}Id"
|
||||
class="device-id"
|
||||
size="small"
|
||||
pill
|
||||
value=${this.deviceID}
|
||||
@sl-change=${this._handleChangeID}
|
||||
>
|
||||
<span slot="prefix">Id</span>
|
||||
<sl-copy-button slot="suffix" value=${this.deviceID}></sl-copy-button>
|
||||
</sl-input>
|
||||
<sl-input id="vmDeviceCard${this.deviceID}Name" class="device-name" size="small" pill placeholder="${this.prefabName}"
|
||||
@sl-change=${this._handleChangeName}>
|
||||
<sl-input
|
||||
id="vmDeviceCard${this.deviceID}Name"
|
||||
class="device-name"
|
||||
size="small"
|
||||
pill
|
||||
placeholder="${this.prefabName}"
|
||||
@sl-change=${this._handleChangeName}
|
||||
>
|
||||
<span slot="prefix">Name</span>
|
||||
<sl-copy-button slot="suffix" from="vmDeviceCard${this.deviceID}Name.value"></sl-copy-button>
|
||||
<sl-copy-button
|
||||
slot="suffix"
|
||||
from="vmDeviceCard${this.deviceID}Name.value"
|
||||
></sl-copy-button>
|
||||
</sl-input>
|
||||
<sl-input id="vmDeviceCard${this.deviceID}NameHash" size="small" pill class="device-name-hash"
|
||||
value="${this.nameHash}" disabled>
|
||||
<sl-input
|
||||
id="vmDeviceCard${this.deviceID}NameHash"
|
||||
size="small"
|
||||
pill
|
||||
class="device-name-hash"
|
||||
value="${this.nameHash}"
|
||||
disabled
|
||||
>
|
||||
<span slot="prefix">Hash</span>
|
||||
<sl-copy-button slot="suffix" from="vmDeviceCard${this.deviceID}NameHash.value"></sl-copy-button>
|
||||
<sl-copy-button
|
||||
slot="suffix"
|
||||
from="vmDeviceCard${this.deviceID}NameHash.value"
|
||||
></sl-copy-button>
|
||||
</sl-input>
|
||||
${badges.map((badge) => badge)}
|
||||
</div>
|
||||
<div class="ms-auto mt-auto mb-auto me-2">
|
||||
<sl-tooltip content=${thisIsActiveIc ? "Removing the selected Active IC is disabled" : "Remove Device"}>
|
||||
<sl-icon-button class="remove-button" name="trash" label="Remove Device" ?disabled=${thisIsActiveIc} @click=${this._handleDeviceRemoveButton}></sl-icon-button>
|
||||
<sl-tooltip
|
||||
content=${thisIsActiveIc
|
||||
? "Removing the selected Active IC is disabled"
|
||||
: "Remove Device"}
|
||||
>
|
||||
<sl-icon-button
|
||||
class="remove-button"
|
||||
name="trash"
|
||||
label="Remove Device"
|
||||
?disabled=${thisIsActiveIc}
|
||||
@click=${this._handleDeviceRemoveButton}
|
||||
></sl-icon-button>
|
||||
</sl-tooltip>
|
||||
</div>
|
||||
`;
|
||||
@@ -231,12 +269,20 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
|
||||
const inputIdBase = `vmDeviceCard${this.deviceID}Field`;
|
||||
return html`
|
||||
${fields.map(([name, field], _index, _fields) => {
|
||||
return html` <sl-input id="${inputIdBase}${name}" key="${name}" value="${field.value}" size="small"
|
||||
@sl-change=${this._handleChangeField}>
|
||||
<span slot="prefix">${name}</span>
|
||||
<sl-copy-button slot="suffix" from="${inputIdBase}${name}.value"></sl-copy-button>
|
||||
<span slot="suffix">${field.field_type}</span>
|
||||
</sl-input>`;
|
||||
return html` <sl-input
|
||||
id="${inputIdBase}${name}"
|
||||
key="${name}"
|
||||
value="${field.value}"
|
||||
size="small"
|
||||
@sl-change=${this._handleChangeField}
|
||||
>
|
||||
<span slot="prefix">${name}</span>
|
||||
<sl-copy-button
|
||||
slot="suffix"
|
||||
from="${inputIdBase}${name}.value"
|
||||
></sl-copy-button>
|
||||
<span slot="suffix">${field.field_type}</span>
|
||||
</sl-input>`;
|
||||
})}
|
||||
`;
|
||||
}
|
||||
@@ -268,21 +314,20 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
|
||||
const slotImg = this.lookupSlotOccupantImg(slot.occupant, slot.typ);
|
||||
return html`
|
||||
<sl-card class="slot-card">
|
||||
<img slot="header" class="slot-header image" src="${slotImg}"
|
||||
onerror="this.src = '${VMDeviceCard.transparentImg}'" />
|
||||
<img slot="header" class="slot-header image" src="${slotImg}" onerror="this.src = '${VMDeviceCard.transparentImg}'" />
|
||||
<span slot="header" class="slot-header">${slotIndex} : ${slot.typ}</span>
|
||||
${
|
||||
typeof slot.occupant !== "undefined"
|
||||
? html`
|
||||
<span slot="header" class="slot-header">
|
||||
Occupant: ${slot.occupant.id} : ${slot.occupant.prefab_hash}
|
||||
</span>
|
||||
<span slot="header" class="slot-header">
|
||||
Quantity: ${slot.occupant.quantity}/
|
||||
${slot.occupant.max_quantity}
|
||||
</span>
|
||||
`
|
||||
: ""
|
||||
typeof slot.occupant !== "undefined"
|
||||
? html`
|
||||
<span slot="header" class="slot-header">
|
||||
Occupant: ${slot.occupant.id} : ${slot.occupant.prefab_hash}
|
||||
</span>
|
||||
<span slot="header" class="slot-header">
|
||||
Quantity: ${slot.occupant.quantity}/
|
||||
${slot.occupant.max_quantity}
|
||||
</span>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
<div class="slot-fields">
|
||||
${fields.map(
|
||||
@@ -313,7 +358,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
|
||||
}
|
||||
|
||||
renderNetworks(): HTMLTemplateResult {
|
||||
const vmNetworks = window.VM!.networks;
|
||||
const vmNetworks = window.VM.vm.networks;
|
||||
const networks = this.connections.map((connection, index, _conns) => {
|
||||
const conn =
|
||||
typeof connection === "object" ? connection.CableNetwork : null;
|
||||
@@ -337,7 +382,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
|
||||
}
|
||||
renderPins(): HTMLTemplateResult {
|
||||
const pins = this.pins;
|
||||
const visibleDevices = window.VM!.visibleDevices(this.deviceID);
|
||||
const visibleDevices = window.VM.vm.visibleDevices(this.deviceID);
|
||||
const pinsHtml = pins?.map(
|
||||
(pin, index) =>
|
||||
html`
|
||||
@@ -371,25 +416,41 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
|
||||
<sl-tab slot="nav" panel="networks">Networks</sl-tab>
|
||||
<sl-tab slot="nav" panel="pins" ?disabled=${!this.pins}>Pins</sl-tab>
|
||||
|
||||
<sl-tab-panel name="fields" active>${this.renderFields()}</sl-tab-panel>
|
||||
<sl-tab-panel name="fields" active
|
||||
>${this.renderFields()}</sl-tab-panel
|
||||
>
|
||||
<sl-tab-panel name="slots">${this.renderSlots()}</sl-tab-panel>
|
||||
<sl-tab-panel name="reagents">${this.renderReagents()}</sl-tab-panel>
|
||||
<sl-tab-panel name="networks">${this.renderNetworks()}</sl-tab-panel>
|
||||
<sl-tab-panel name="pins">${this.renderPins()}</sl-tab-panel>
|
||||
</sl-tab-group>
|
||||
</ic10-details>
|
||||
<sl-dialog class="remove-device-dialog" no-header @sl-request-close=${this._preventOverlayClose}>
|
||||
<sl-dialog
|
||||
class="remove-device-dialog"
|
||||
no-header
|
||||
@sl-request-close=${this._preventOverlayClose}
|
||||
>
|
||||
<div class="remove-dialog-body">
|
||||
<img class="dialog-image mt-auto mb-auto me-2" src="img/stationpedia/${this.prefabName}.png"
|
||||
onerror="this.src = '${VMDeviceCard.transparentImg}'" />
|
||||
<img
|
||||
class="dialog-image mt-auto mb-auto me-2"
|
||||
src="img/stationpedia/${this.prefabName}.png"
|
||||
onerror="this.src = '${VMDeviceCard.transparentImg}'"
|
||||
/>
|
||||
<div class="flex-g">
|
||||
<p><strong>Are you sure you want to remove this device?</strong></p>
|
||||
<span>Id ${this.deviceID} : ${this.name ?? this.prefabName}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<sl-button variant="primary" autofocus @click=${this._closeRemoveDialog}>Close</sl-button>
|
||||
<sl-button variant="danger" @click=${this._removeDialogRemove}>Remove</sl-button>
|
||||
<sl-button
|
||||
variant="primary"
|
||||
autofocus
|
||||
@click=${this._closeRemoveDialog}
|
||||
>Close</sl-button
|
||||
>
|
||||
<sl-button variant="danger" @click=${this._removeDialogRemove}
|
||||
>Remove</sl-button
|
||||
>
|
||||
</div>
|
||||
</sl-dialog>
|
||||
`;
|
||||
@@ -398,22 +459,24 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
|
||||
@query(".remove-device-dialog") removeDialog: SlDialog;
|
||||
|
||||
_preventOverlayClose(event: CustomEvent) {
|
||||
if (event.detail.source === 'overlay') {
|
||||
if (event.detail.source === "overlay") {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
_closeRemoveDialog() {
|
||||
this.removeDialog.hide()
|
||||
this.removeDialog.hide();
|
||||
}
|
||||
|
||||
_handleChangeID(e: CustomEvent) {
|
||||
const input = e.target as SlInput;
|
||||
const val = parseIntWithHexOrBinary(input.value);
|
||||
if (!isNaN(val)) {
|
||||
if (!window.VM.changeDeviceId(this.deviceID, val)) {
|
||||
input.value = this.deviceID.toString();
|
||||
}
|
||||
window.VM.get().then(vm => {
|
||||
if (!vm.changeDeviceId(this.deviceID, val)) {
|
||||
input.value = this.deviceID.toString();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
input.value = this.deviceID.toString();
|
||||
}
|
||||
@@ -422,20 +485,24 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
|
||||
_handleChangeName(e: CustomEvent) {
|
||||
const input = e.target as SlInput;
|
||||
const name = input.value.length === 0 ? undefined : input.value;
|
||||
if (!window.VM?.setDeviceName(this.deviceID, name)) {
|
||||
input.value = this.name;
|
||||
};
|
||||
this.updateDevice();
|
||||
window.VM.get().then(vm => {
|
||||
if (!vm.setDeviceName(this.deviceID, name)) {
|
||||
input.value = this.name;
|
||||
}
|
||||
this.updateDevice();
|
||||
});
|
||||
}
|
||||
|
||||
_handleChangeField(e: CustomEvent) {
|
||||
const input = e.target as SlInput;
|
||||
const field = input.getAttribute("key")! as LogicType;
|
||||
const val = parseNumber(input.value);
|
||||
if (!window.VM?.setDeviceField(this.deviceID, field, val, true)) {
|
||||
input.value = this.fields.get(field).value.toString();
|
||||
}
|
||||
this.updateDevice();
|
||||
window.VM.get().then((vm) => {
|
||||
if (!vm.setDeviceField(this.deviceID, field, val, true)) {
|
||||
input.value = this.fields.get(field).value.toString();
|
||||
}
|
||||
this.updateDevice();
|
||||
});
|
||||
}
|
||||
|
||||
_handleChangeSlotField(e: CustomEvent) {
|
||||
@@ -443,26 +510,30 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
|
||||
const slot = parseInt(input.getAttribute("slotIndex")!);
|
||||
const field = input.getAttribute("key")! as SlotLogicType;
|
||||
const val = parseNumber(input.value);
|
||||
if (!window.VM?.setDeviceSlotField(this.deviceID, slot, field, val, true)) {
|
||||
input.value = this.device.getSlotField(slot, field).toString();
|
||||
}
|
||||
this.updateDevice();
|
||||
window.VM.get().then((vm) => {
|
||||
if (!vm.setDeviceSlotField(this.deviceID, slot, field, val, true)) {
|
||||
input.value = this.device.getSlotField(slot, field).toString();
|
||||
}
|
||||
this.updateDevice();
|
||||
});
|
||||
}
|
||||
|
||||
_handleDeviceRemoveButton(_e: Event) {
|
||||
this.removeDialog.show()
|
||||
this.removeDialog.show();
|
||||
}
|
||||
|
||||
_removeDialogRemove() {
|
||||
this.removeDialog.hide()
|
||||
window.VM.removeDevice(this.deviceID)
|
||||
this.removeDialog.hide();
|
||||
window.VM.get().then((vm) => vm.removeDevice(this.deviceID));
|
||||
}
|
||||
|
||||
_handleChangeConnection(e: CustomEvent) {
|
||||
const select = e.target as SlSelect;
|
||||
const conn = parseInt(select.getAttribute("key")!);
|
||||
const val = select.value ? parseInt(select.value as string) : undefined;
|
||||
window.VM.setDeviceConnection(this.deviceID, conn, val);
|
||||
window.VM.get().then((vm) =>
|
||||
vm.setDeviceConnection(this.deviceID, conn, val),
|
||||
);
|
||||
this.updateDevice();
|
||||
}
|
||||
|
||||
@@ -470,7 +541,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
|
||||
const select = e.target as SlSelect;
|
||||
const pin = parseInt(select.getAttribute("key")!);
|
||||
const val = select.value ? parseInt(select.value as string) : undefined;
|
||||
window.VM.setDevicePin(this.deviceID, pin, val);
|
||||
window.VM.get().then((vm) => vm.setDevicePin(this.deviceID, pin, val));
|
||||
this.updateDevice();
|
||||
}
|
||||
}
|
||||
@@ -507,14 +578,16 @@ export class VMDeviceList extends BaseElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.devices = [...window.VM!.deviceIds];
|
||||
this.devices = [...window.VM.vm.deviceIds];
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
const root = super.connectedCallback();
|
||||
window.VM?.addEventListener(
|
||||
"vm-devices-update",
|
||||
this._handleDevicesUpdate.bind(this),
|
||||
window.VM.get().then((vm) =>
|
||||
vm.addEventListener(
|
||||
"vm-devices-update",
|
||||
this._handleDevicesUpdate.bind(this),
|
||||
),
|
||||
);
|
||||
return root;
|
||||
}
|
||||
@@ -528,9 +601,12 @@ export class VMDeviceList extends BaseElement {
|
||||
}
|
||||
|
||||
protected render(): HTMLTemplateResult {
|
||||
const deviceCards: HTMLTemplateResult[] = this.filteredDeviceIds.map(
|
||||
(id, _index, _ids) =>
|
||||
html`<vm-device-card .deviceID=${id} class="device-list-card" > </vm-device-card>`,
|
||||
const deviceCards = repeat(
|
||||
this.filteredDeviceIds,
|
||||
(id) => id,
|
||||
(id) =>
|
||||
html`<vm-device-card .deviceID=${id} class="device-list-card">
|
||||
</vm-device-card>`,
|
||||
);
|
||||
const result = html`
|
||||
<div class="header">
|
||||
@@ -538,7 +614,12 @@ export class VMDeviceList extends BaseElement {
|
||||
Devices:
|
||||
<sl-badge variant="neutral" pill>${this.devices.length}</sl-badge>
|
||||
</span>
|
||||
<sl-input class="device-filter-input" placeholder="Filter Devices" clearable @sl-input=${this._handleFilterInput}>
|
||||
<sl-input
|
||||
class="device-filter-input"
|
||||
placeholder="Filter Devices"
|
||||
clearable
|
||||
@sl-input=${this._handleFilterInput}
|
||||
>
|
||||
<sl-icon slot="suffix" name="search"></sl-icon>"
|
||||
</sl-input>
|
||||
<vm-add-device-button class="ms-auto"></vm-add-device-button>
|
||||
@@ -588,7 +669,7 @@ export class VMDeviceList extends BaseElement {
|
||||
if (this._filter) {
|
||||
const datapoints: [string, number][] = [];
|
||||
for (const device_id of this.devices) {
|
||||
const device = window.VM.devices.get(device_id);
|
||||
const device = window.VM.vm.devices.get(device_id);
|
||||
if (device) {
|
||||
if (typeof device.name !== "undefined") {
|
||||
datapoints.push([device.name, device.id]);
|
||||
@@ -720,9 +801,11 @@ export class VMAddDeviceButton extends BaseElement {
|
||||
|
||||
connectedCallback(): void {
|
||||
const root = super.connectedCallback();
|
||||
window.VM!.addEventListener(
|
||||
"vm-device-db-loaded",
|
||||
this._handleDeviceDBLoad.bind(this),
|
||||
window.VM.get().then((vm) =>
|
||||
vm.addEventListener(
|
||||
"vm-device-db-loaded",
|
||||
this._handleDeviceDBLoad.bind(this),
|
||||
),
|
||||
);
|
||||
return root;
|
||||
}
|
||||
@@ -734,7 +817,11 @@ export class VMAddDeviceButton extends BaseElement {
|
||||
renderSearchResults(): HTMLTemplateResult {
|
||||
const renderedResults: HTMLTemplateResult[] = this._searchResults?.map(
|
||||
(result) => html`
|
||||
<vm-device-template prefab_name=${result.name} class="card" @add-device-template=${this._handleDeviceAdd}>
|
||||
<vm-device-template
|
||||
prefab_name=${result.name}
|
||||
class="card"
|
||||
@add-device-template=${this._handleDeviceAdd}
|
||||
>
|
||||
</vm-device-template>
|
||||
`,
|
||||
);
|
||||
@@ -747,20 +834,33 @@ export class VMAddDeviceButton extends BaseElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<sl-button variant="neutral" outline pill @click=${this._handleAddButtonClick}>
|
||||
<sl-button
|
||||
variant="neutral"
|
||||
outline
|
||||
pill
|
||||
@click=${this._handleAddButtonClick}
|
||||
>
|
||||
Add Device
|
||||
</sl-button>
|
||||
<sl-drawer class="add-device-drawer" placement="bottom" no-header>
|
||||
<sl-input class="device-search-input" autofocus placeholder="Search For Device" clearable
|
||||
@sl-input=${this._handleSearchInput}>
|
||||
<sl-input
|
||||
class="device-search-input"
|
||||
autofocus
|
||||
placeholder="Search For Device"
|
||||
clearable
|
||||
@sl-input=${this._handleSearchInput}
|
||||
>
|
||||
<span slot="prefix">Search Structures</span>
|
||||
<sl-icon slot="suffix" name="search"></sl-icon>"
|
||||
</sl-input>
|
||||
<div class="search-results">${this.renderSearchResults()}</div>
|
||||
<sl-button slot="footer" variant="primary" @click=${()=> {
|
||||
this.drawer.hide();
|
||||
<sl-button
|
||||
slot="footer"
|
||||
variant="primary"
|
||||
@click=${() => {
|
||||
this.drawer.hide();
|
||||
}}
|
||||
>
|
||||
>
|
||||
Close
|
||||
</sl-button>
|
||||
</sl-drawer>
|
||||
@@ -780,7 +880,7 @@ export class VMAddDeviceButton extends BaseElement {
|
||||
|
||||
_handleAddButtonClick() {
|
||||
this.drawer.show();
|
||||
(this.drawer.querySelector('.device-search-input') as SlInput).select();
|
||||
(this.drawer.querySelector(".device-search-input") as SlInput).select();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -826,7 +926,8 @@ export class VmDeviceTemplate extends BaseElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.deviceDB = window.VM!.db;
|
||||
const that = this;
|
||||
window.VM.get().then((vm) => (that.deviceDB = vm.db));
|
||||
}
|
||||
|
||||
get deviceDB(): DeviceDB {
|
||||
@@ -900,9 +1001,11 @@ export class VmDeviceTemplate extends BaseElement {
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
window.VM!.addEventListener(
|
||||
"vm-device-db-loaded",
|
||||
this._handleDeviceDBLoad.bind(this),
|
||||
window.VM.get().then((vm) =>
|
||||
vm.addEventListener(
|
||||
"vm-device-db-loaded",
|
||||
this._handleDeviceDBLoad.bind(this),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -914,12 +1017,18 @@ export class VmDeviceTemplate extends BaseElement {
|
||||
const fields = Object.entries(this.fields);
|
||||
return html`
|
||||
${fields.map(([name, field], _index, _fields) => {
|
||||
return html`
|
||||
<sl-input key="${name}" value="${field.value}" size="small" @sl-change=${this._handleChangeField} ?disabled=${name==="PrefabHash"} >
|
||||
<span slot="prefix">${name}</span>
|
||||
<span slot="suffix">${field.field_type}</span>
|
||||
</sl-input>
|
||||
`;
|
||||
return html`
|
||||
<sl-input
|
||||
key="${name}"
|
||||
value="${field.value}"
|
||||
size="small"
|
||||
@sl-change=${this._handleChangeField}
|
||||
?disabled=${name === "PrefabHash"}
|
||||
>
|
||||
<span slot="prefix">${name}</span>
|
||||
<span slot="suffix">${field.field_type}</span>
|
||||
</sl-input>
|
||||
`;
|
||||
})}
|
||||
`;
|
||||
}
|
||||
@@ -947,25 +1056,33 @@ export class VmDeviceTemplate extends BaseElement {
|
||||
return html``;
|
||||
}
|
||||
|
||||
renderNetworks(): HTMLTemplateResult {
|
||||
const vmNetworks = window.VM!.networks;
|
||||
renderNetworks() {
|
||||
const vm = window.VM.vm;
|
||||
const vmNetworks = vm.networks;
|
||||
const connections = this.connections;
|
||||
return html`
|
||||
<div class="networks">
|
||||
${connections.map((connection, index, _conns) => {
|
||||
const conn =
|
||||
typeof connection === "object" ? connection.CableNetwork : null;
|
||||
return html`
|
||||
<sl-select hoist placement="top" clearable key=${index} value=${conn?.net} ?disabled=${conn===null}
|
||||
@sl-change=${this._handleChangeConnection}>
|
||||
<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>
|
||||
`;
|
||||
const conn =
|
||||
typeof connection === "object" ? connection.CableNetwork : null;
|
||||
return html`
|
||||
<sl-select
|
||||
hoist
|
||||
placement="top"
|
||||
clearable
|
||||
key=${index}
|
||||
value=${conn?.net}
|
||||
?disabled=${conn === null}
|
||||
@sl-change=${this._handleChangeConnection}
|
||||
>
|
||||
<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>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
@@ -990,16 +1107,23 @@ export class VmDeviceTemplate extends BaseElement {
|
||||
<sl-card class="template-card">
|
||||
<div class="header" slot="header">
|
||||
<sl-tooltip content="${device?.name}">
|
||||
<img class="image" src="img/stationpedia/${device?.name}.png"
|
||||
onerror="this.src = '${VMDeviceCard.transparentImg}'" />
|
||||
<img
|
||||
class="image"
|
||||
src="img/stationpedia/${device?.name}.png"
|
||||
onerror="this.src = '${VMDeviceCard.transparentImg}'"
|
||||
/>
|
||||
</sl-tooltip>
|
||||
<div class="vstack">
|
||||
<span class="prefab-title">${device.title}</span>
|
||||
<span class="prefab-name"><small>${device?.name}</small></span>
|
||||
<span class="prefab-hash"><small>${device?.hash}</small></span>
|
||||
</div>
|
||||
<sl-button class="ms-auto mt-auto mb-auto" pill variant="success" @click=${this._handleAddButtonClick}>Add <sl-icon slot="prefix"
|
||||
name="plus-lg"></sl-icon>
|
||||
<sl-button
|
||||
class="ms-auto mt-auto mb-auto"
|
||||
pill
|
||||
variant="success"
|
||||
@click=${this._handleAddButtonClick}
|
||||
>Add <sl-icon slot="prefix" name="plus-lg"></sl-icon>
|
||||
</sl-button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@@ -1013,7 +1137,9 @@ export class VmDeviceTemplate extends BaseElement {
|
||||
<sl-tab-panel name="fields">${this.renderFields()}</sl-tab-panel>
|
||||
<sl-tab-panel name="slots">${this.renderSlots()}</sl-tab-panel>
|
||||
<!-- <sl-tab-panel name="reagents">${this.renderReagents()}</sl-tab-panel> -->
|
||||
<sl-tab-panel name="networks">${this.renderNetworks()}</sl-tab-panel>
|
||||
<sl-tab-panel name="networks"
|
||||
>${this.renderNetworks()}</sl-tab-panel
|
||||
>
|
||||
<!-- <sl-tab-panel name="pins">${this.renderPins()}</sl-tab-panel> -->
|
||||
</sl-tab-group>
|
||||
</div>
|
||||
@@ -1032,7 +1158,7 @@ export class VmDeviceTemplate extends BaseElement {
|
||||
connections: this.connections,
|
||||
fields: this.fields,
|
||||
};
|
||||
window.VM.addDeviceFromTemplate(template);
|
||||
window.VM.vm.addDeviceFromTemplate(template);
|
||||
|
||||
// reset state for new device
|
||||
this.setupState();
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { DeviceRef, DeviceTemplate, LogicType, SlotLogicType, VMRef, init } from "ic10emu_wasm";
|
||||
import {
|
||||
DeviceRef,
|
||||
DeviceTemplate,
|
||||
FrozenVM,
|
||||
LogicType,
|
||||
SlotLogicType,
|
||||
VMRef,
|
||||
init,
|
||||
} from "ic10emu_wasm";
|
||||
import { DeviceDB } from "./device_db";
|
||||
import "./base_device";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
VM?: VirtualMachine;
|
||||
}
|
||||
}
|
||||
|
||||
import { fromJson, toJson } from "../utils";
|
||||
import { App } from "../app";
|
||||
export interface ToastMessage {
|
||||
variant: "warning" | "danger" | "success" | "primary" | "neutral";
|
||||
icon: string;
|
||||
@@ -24,11 +27,13 @@ class VirtualMachine extends EventTarget {
|
||||
accessor db: DeviceDB;
|
||||
dbPromise: Promise<{ default: DeviceDB }>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const vm = init();
|
||||
private app: App;
|
||||
|
||||
window.VM = this;
|
||||
constructor(app: App) {
|
||||
super();
|
||||
this.app = app;
|
||||
const vm = init();
|
||||
window.VM.set(this);
|
||||
|
||||
this.ic10vm = vm;
|
||||
|
||||
@@ -74,7 +79,7 @@ class VirtualMachine extends EventTarget {
|
||||
}
|
||||
|
||||
get activeIC() {
|
||||
return this._ics.get(window.App!.session.activeIC);
|
||||
return this._ics.get(this.app.session.activeIC);
|
||||
}
|
||||
|
||||
visibleDevices(source: number) {
|
||||
@@ -126,7 +131,7 @@ class VirtualMachine extends EventTarget {
|
||||
}
|
||||
|
||||
updateCode() {
|
||||
const progs = window.App!.session.programs;
|
||||
const progs = this.app.session.programs;
|
||||
for (const id of progs.keys()) {
|
||||
const attempt = Date.now().toString(16);
|
||||
const ic = this._ics.get(id);
|
||||
@@ -136,13 +141,13 @@ class VirtualMachine extends EventTarget {
|
||||
console.time(`CompileProgram_${id}_${attempt}`);
|
||||
this.ics.get(id)!.setCodeInvalid(progs.get(id)!);
|
||||
const compiled = this.ics.get(id)?.program!;
|
||||
window.App?.session.setProgramErrors(id, compiled.errors);
|
||||
this.app.session.setProgramErrors(id, compiled.errors);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-device-modified", { detail: id }),
|
||||
);
|
||||
} catch (err) {
|
||||
this.handleVmError(err);
|
||||
} finally{
|
||||
} finally {
|
||||
console.timeEnd(`CompileProgram_${id}_${attempt}`);
|
||||
}
|
||||
}
|
||||
@@ -205,7 +210,7 @@ class VirtualMachine extends EventTarget {
|
||||
new CustomEvent("vm-device-modified", { detail: device.id }),
|
||||
);
|
||||
if (typeof device.ic !== "undefined") {
|
||||
window.App!.session.setActiveLine(device.id, device.ip!);
|
||||
this.app.session.setActiveLine(device.id, device.ip!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,8 +230,8 @@ class VirtualMachine extends EventTarget {
|
||||
try {
|
||||
this.ic10vm.changeDeviceId(old_id, new_id);
|
||||
this.updateDevices();
|
||||
if (window.App.session.activeIC === old_id) {
|
||||
window.App.session.activeIC = new_id;
|
||||
if (this.app.session.activeIC === old_id) {
|
||||
this.app.session.activeIC = new_id;
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
@@ -264,16 +269,23 @@ class VirtualMachine extends EventTarget {
|
||||
if (device) {
|
||||
try {
|
||||
device.setName(name);
|
||||
this.dispatchEvent(new CustomEvent("vm-device-modified", { detail: id }));
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-device-modified", { detail: id }),
|
||||
);
|
||||
return true;
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
this.handleVmError(e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setDeviceField(id: number, field: LogicType, val: number, force?: boolean): boolean {
|
||||
setDeviceField(
|
||||
id: number,
|
||||
field: LogicType,
|
||||
val: number,
|
||||
force?: boolean,
|
||||
): boolean {
|
||||
force = force ?? false;
|
||||
const device = this._devices.get(id);
|
||||
if (device) {
|
||||
@@ -288,7 +300,13 @@ class VirtualMachine extends EventTarget {
|
||||
return false;
|
||||
}
|
||||
|
||||
setDeviceSlotField(id: number, slot: number, field: SlotLogicType, val: number, force?: boolean): boolean {
|
||||
setDeviceSlotField(
|
||||
id: number,
|
||||
slot: number,
|
||||
field: SlotLogicType,
|
||||
val: number,
|
||||
force?: boolean,
|
||||
): boolean {
|
||||
force = force ?? false;
|
||||
const device = this._devices.get(id);
|
||||
if (device) {
|
||||
@@ -303,18 +321,22 @@ class VirtualMachine extends EventTarget {
|
||||
return false;
|
||||
}
|
||||
|
||||
setDeviceConnection(id: number, conn: number, val: number | undefined): boolean {
|
||||
setDeviceConnection(
|
||||
id: number,
|
||||
conn: number,
|
||||
val: number | undefined,
|
||||
): boolean {
|
||||
const device = this._devices.get(id);
|
||||
if (typeof device !== "undefined") {
|
||||
try {
|
||||
this.ic10vm.setDeviceConnection(id, conn, val);
|
||||
this.updateDevice(device);
|
||||
return true
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.handleVmError(err);
|
||||
}
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
setDevicePin(id: number, pin: number, val: number | undefined): boolean {
|
||||
@@ -367,6 +389,33 @@ class VirtualMachine extends EventTarget {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
saveVMState(): FrozenVM {
|
||||
return this.ic10vm.saveVMState();
|
||||
}
|
||||
|
||||
restoreVMState(state: FrozenVM) {
|
||||
try {
|
||||
this.ic10vm.restoreVMState(state);
|
||||
this._devices = new Map();
|
||||
this._ics = new Map();
|
||||
this.updateDevices();
|
||||
} catch (e) {
|
||||
this.handleVmError(e);
|
||||
}
|
||||
}
|
||||
|
||||
getPrograms() {
|
||||
const programs: [number, string][] = Array.from(this._ics.entries()).map(
|
||||
([id, ic]) => [id, ic.code],
|
||||
);
|
||||
return programs;
|
||||
}
|
||||
}
|
||||
|
||||
export interface VMState {
|
||||
activeIC: number;
|
||||
vm: FrozenVM;
|
||||
}
|
||||
|
||||
export { VirtualMachine };
|
||||
|
||||
@@ -104,6 +104,6 @@ export class VMICRegisters extends VMActiveICMixin(BaseElement) {
|
||||
const input = e.target as SlInput;
|
||||
const index = parseInt(input.getAttribute("key")!);
|
||||
const val = parseNumber(input.value);
|
||||
window.VM!.setRegister(index, val);
|
||||
window.VM.vm.setRegister(index, val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,6 @@ export class VMICStack extends VMActiveICMixin(BaseElement) {
|
||||
const input = e.target as SlInput;
|
||||
const index = parseInt(input.getAttribute("key")!);
|
||||
const val = parseNumber(input.value);
|
||||
window.VM!.setStack(index, val);
|
||||
window.VM.get().then(vm => vm.setStack(index, val));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ export class VMUI extends BaseElement {
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
window.VM.addEventListener("vm-message", this._handleVMMessage.bind(this));
|
||||
window.VM.get().then(vm => vm.addEventListener("vm-message", this._handleVMMessage.bind(this)));
|
||||
}
|
||||
|
||||
_handleVMMessage(e: CustomEvent) {
|
||||
|
||||
@@ -6,7 +6,7 @@ export function connectionFromDeviceDBConnection(conn: DeviceDBConnection): Conn
|
||||
if (CableNetworkTypes.includes(conn.typ)) {
|
||||
return {
|
||||
CableNetwork: {
|
||||
net: window.VM?.ic10vm.defaultNetwork,
|
||||
net: window.VM.vm.ic10vm.defaultNetwork,
|
||||
typ: conn.typ
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user