device component with event to update state
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
@@ -50,7 +50,7 @@ fn write_repr_enum<T: std::io::Write, I, P>(
|
||||
)
|
||||
.unwrap();
|
||||
for (name, variant) in variants {
|
||||
let variant_name = name.to_case(Case::Pascal);
|
||||
let variant_name = name.replace('.', "").to_case(Case::Pascal);
|
||||
let mut serialize = vec![name.clone()];
|
||||
serialize.extend(variant.aliases.iter().cloned());
|
||||
let serialize_str = serialize
|
||||
@@ -186,31 +186,31 @@ fn write_enums() {
|
||||
let output_file = File::create(dest_path).unwrap();
|
||||
let mut writer = BufWriter::new(&output_file);
|
||||
|
||||
let mut enums_lookup_map_builder = ::phf_codegen::Map::new();
|
||||
let mut check_set = std::collections::HashSet::new();
|
||||
let mut enums_map: HashMap<String, EnumVariant<u16>> = HashMap::new();
|
||||
let e_infile = Path::new("data/enums.txt");
|
||||
let e_contents = fs::read_to_string(e_infile).unwrap();
|
||||
|
||||
for line in e_contents.lines().filter(|l| !l.trim().is_empty()) {
|
||||
let (name, val_str) = line.split_once(' ').unwrap();
|
||||
let mut it = line.splitn(3, ' ');
|
||||
let name = it.next().unwrap();
|
||||
let val_str = it.next().unwrap();
|
||||
let val: Option<u16> = val_str.parse().ok();
|
||||
let docs = it.next();
|
||||
let deprecated = docs
|
||||
.map(|docs| docs.trim().to_uppercase() == "DEPRECATED")
|
||||
.unwrap_or(false);
|
||||
|
||||
let val: Option<u8> = val_str.parse().ok();
|
||||
|
||||
if !check_set.contains(name) {
|
||||
check_set.insert(name);
|
||||
}
|
||||
|
||||
if let Some(v) = val {
|
||||
enums_lookup_map_builder.entry(name, &format!("{}u8", v));
|
||||
}
|
||||
enums_map.insert(
|
||||
name.to_string(),
|
||||
EnumVariant {
|
||||
aliases: Vec::new(),
|
||||
value: val,
|
||||
deprecated,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
writeln!(
|
||||
&mut writer,
|
||||
"pub(crate) const ENUM_LOOKUP: phf::Map<&'static str, u8> = {};",
|
||||
enums_lookup_map_builder.build()
|
||||
)
|
||||
.unwrap();
|
||||
write_repr_enum(&mut writer, "LogicEnums", &enums_map, true);
|
||||
|
||||
println!("cargo:rerun-if-changed=data/enums.txt");
|
||||
}
|
||||
|
||||
@@ -760,8 +760,10 @@ impl FromStr for Operand {
|
||||
}
|
||||
} else if let Some(val) = CONSTANTS_LOOKUP.get(s) {
|
||||
Ok(Operand::Number(Number::Constant(*val)))
|
||||
} else if let Some(val) = ENUM_LOOKUP.get(s) {
|
||||
Ok(Operand::Number(Number::Enum(*val as f64)))
|
||||
} else if let Ok(val) = LogicEnums::from_str(s) {
|
||||
Ok(Operand::Number(Number::Enum(
|
||||
val.get_str("value").unwrap().parse().unwrap(),
|
||||
)))
|
||||
} else if let Ok(lt) = LogicType::from_str(s) {
|
||||
Ok(Operand::LogicType(lt))
|
||||
} else if let Ok(slt) = SlotLogicType::from_str(s) {
|
||||
@@ -1127,9 +1129,7 @@ mod tests {
|
||||
indirection: 0,
|
||||
target: 2,
|
||||
}),
|
||||
Operand::Identifier(Identifier {
|
||||
name: "LogicType.Temperature".to_owned()
|
||||
}),
|
||||
Operand::Number(Number::Enum(6.0)),
|
||||
],
|
||||
},),),
|
||||
comment: None,
|
||||
|
||||
@@ -58,7 +58,7 @@ pub enum ICError {
|
||||
index: u32,
|
||||
desired: String,
|
||||
},
|
||||
#[error("Unknown identifier '{0}")]
|
||||
#[error("Unknown identifier {0}")]
|
||||
UnknownIdentifier(String),
|
||||
#[error("Device Not Set")]
|
||||
DeviceNotSet,
|
||||
@@ -2030,9 +2030,9 @@ impl IC {
|
||||
let RegisterSpec {
|
||||
indirection,
|
||||
target,
|
||||
|
||||
} = reg.as_register(this, inst, 1)?;
|
||||
let (Some(device_id), _connection) = dev_id.as_device(this, inst, 2)? else {
|
||||
let (Some(device_id), _connection) = dev_id.as_device(this, inst, 2)?
|
||||
else {
|
||||
return Err(DeviceNotSet);
|
||||
};
|
||||
let device = vm.get_device_same_network(this.device, device_id);
|
||||
@@ -2057,9 +2057,9 @@ impl IC {
|
||||
let RegisterSpec {
|
||||
indirection,
|
||||
target,
|
||||
|
||||
} = reg.as_register(this, inst, 1)?;
|
||||
let (Some(device_id), _connection) = dev_id.as_device(this, inst, 2)? else {
|
||||
let (Some(device_id), _connection) = dev_id.as_device(this, inst, 2)?
|
||||
else {
|
||||
return Err(DeviceNotSet);
|
||||
};
|
||||
let device = vm.get_device_same_network(this.device, device_id);
|
||||
@@ -2080,9 +2080,9 @@ impl IC {
|
||||
oprs => Err(ICError::mismatch_operands(oprs.len(), 3)),
|
||||
},
|
||||
Put => match &operands[..] {
|
||||
|
||||
[dev_id, addr, val] => {
|
||||
let (Some(device_id), _connection) = dev_id.as_device(this, inst, 1)? else {
|
||||
let (Some(device_id), _connection) = dev_id.as_device(this, inst, 1)?
|
||||
else {
|
||||
return Err(DeviceNotSet);
|
||||
};
|
||||
let device = vm.get_device_same_network(this.device, device_id);
|
||||
@@ -2093,6 +2093,7 @@ impl IC {
|
||||
let val = val.as_value(this, inst, 3)?;
|
||||
let mut ic = vm.ics.get(ic_id).unwrap().borrow_mut();
|
||||
ic.poke(addr, val)?;
|
||||
vm.set_modified(device_id);
|
||||
Ok(())
|
||||
}
|
||||
None => Err(DeviceHasNoIC),
|
||||
@@ -2116,6 +2117,7 @@ impl IC {
|
||||
let val = val.as_value(this, inst, 3)?;
|
||||
let mut ic = vm.ics.get(ic_id).unwrap().borrow_mut();
|
||||
ic.poke(addr, val)?;
|
||||
vm.set_modified(device_id as u16);
|
||||
Ok(())
|
||||
}
|
||||
None => Err(DeviceHasNoIC),
|
||||
@@ -2150,6 +2152,7 @@ impl IC {
|
||||
Some(device) => {
|
||||
let val = val.as_value(this, inst, 1)?;
|
||||
device.borrow_mut().set_field(lt, val)?;
|
||||
vm.set_modified(device_id);
|
||||
Ok(())
|
||||
}
|
||||
None => Err(UnknownDeviceID(device_id as f64)),
|
||||
@@ -2169,6 +2172,7 @@ impl IC {
|
||||
let lt = LogicType::try_from(lt.as_value(this, inst, 2)?)?;
|
||||
let val = val.as_value(this, inst, 3)?;
|
||||
device.borrow_mut().set_field(lt, val)?;
|
||||
vm.set_modified(device_id as u16);
|
||||
Ok(())
|
||||
}
|
||||
None => Err(UnknownDeviceID(device_id)),
|
||||
@@ -2188,6 +2192,7 @@ impl IC {
|
||||
let lt = SlotLogicType::try_from(lt.as_value(this, inst, 3)?)?;
|
||||
let val = val.as_value(this, inst, 4)?;
|
||||
device.borrow_mut().set_slot_field(index, lt, val)?;
|
||||
vm.set_modified(device_id);
|
||||
Ok(())
|
||||
}
|
||||
None => Err(UnknownDeviceID(device_id as f64)),
|
||||
@@ -2410,8 +2415,8 @@ impl IC {
|
||||
let result = process_op(self);
|
||||
if result.is_ok() || advance_ip_on_err {
|
||||
self.ic += 1;
|
||||
self.ip = next_ip;
|
||||
}
|
||||
self.ip = next_ip;
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,6 +200,9 @@ pub struct VM {
|
||||
id_gen: IdSequenceGenerator,
|
||||
network_id_gen: IdSequenceGenerator,
|
||||
random: Rc<RefCell<crate::rand_mscorlib::Random>>,
|
||||
|
||||
/// list of device id's touched on the last operation
|
||||
operation_modified: RefCell<Vec<u16>>,
|
||||
}
|
||||
|
||||
impl Default for Network {
|
||||
@@ -410,6 +413,7 @@ impl VM {
|
||||
id_gen,
|
||||
network_id_gen,
|
||||
random: Rc::new(RefCell::new(crate::rand_mscorlib::Random::new())),
|
||||
operation_modified: RefCell::new(Vec::new()),
|
||||
};
|
||||
let _ = vm.add_ic(None);
|
||||
vm
|
||||
@@ -555,9 +559,16 @@ impl VM {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// returns a list of device ids modified in the last operations
|
||||
pub fn last_operation_modified(&self) -> Vec<u16> {
|
||||
self.operation_modified.borrow().clone()
|
||||
}
|
||||
|
||||
pub fn step_ic(&self, id: u16, advance_ip_on_err: bool) -> Result<bool, VMError> {
|
||||
self.operation_modified.borrow_mut().clear();
|
||||
let device = self.devices.get(&id).ok_or(VMError::UnknownId(id))?.clone();
|
||||
let ic_id = *device.borrow().ic.as_ref().ok_or(VMError::NoIC(id))?;
|
||||
self.set_modified(id);
|
||||
let ic = self
|
||||
.ics
|
||||
.get(&ic_id)
|
||||
@@ -570,6 +581,7 @@ impl VM {
|
||||
|
||||
/// returns true if executed 128 lines, false if returned early.
|
||||
pub fn run_ic(&self, id: u16, ignore_errors: bool) -> Result<bool, VMError> {
|
||||
self.operation_modified.borrow_mut().clear();
|
||||
let device = self.devices.get(&id).ok_or(VMError::UnknownId(id))?.clone();
|
||||
let ic_id = *device.borrow().ic.as_ref().ok_or(VMError::NoIC(id))?;
|
||||
let ic = self
|
||||
@@ -578,6 +590,7 @@ impl VM {
|
||||
.ok_or(VMError::UnknownIcId(ic_id))?
|
||||
.clone();
|
||||
ic.borrow_mut().ic = 0;
|
||||
self.set_modified(id);
|
||||
for _i in 0..128 {
|
||||
if let Err(err) = ic.borrow_mut().step(self, ignore_errors) {
|
||||
if !ignore_errors {
|
||||
@@ -594,6 +607,10 @@ impl VM {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn set_modified(&self, id: u16) {
|
||||
self.operation_modified.borrow_mut().push(id);
|
||||
}
|
||||
|
||||
pub fn reset_ic(&self, id: u16) -> Result<bool, VMError> {
|
||||
let device = self.devices.get(&id).ok_or(VMError::UnknownId(id))?.clone();
|
||||
let ic_id = *device.borrow().ic.as_ref().ok_or(VMError::NoIC(id))?;
|
||||
@@ -666,6 +683,7 @@ impl VM {
|
||||
}
|
||||
|
||||
fn add_device_to_network(&self, id: u16, network_id: u16) -> Result<bool, VMError> {
|
||||
self.set_modified(id);
|
||||
if !self.devices.contains_key(&id) {
|
||||
return Err(VMError::UnknownId(id));
|
||||
};
|
||||
@@ -685,7 +703,10 @@ impl VM {
|
||||
val: f64,
|
||||
) -> Result<(), ICError> {
|
||||
self.batch_device(source, prefab, None)
|
||||
.map(|device| device.borrow_mut().set_field(typ, val))
|
||||
.map(|device| {
|
||||
self.set_modified(device.borrow().id);
|
||||
device.borrow_mut().set_field(typ, val)
|
||||
})
|
||||
.try_collect()
|
||||
}
|
||||
|
||||
@@ -698,7 +719,10 @@ impl VM {
|
||||
val: f64,
|
||||
) -> Result<(), ICError> {
|
||||
self.batch_device(source, prefab, None)
|
||||
.map(|device| device.borrow_mut().set_slot_field(index, typ, val))
|
||||
.map(|device| {
|
||||
self.set_modified(device.borrow().id);
|
||||
device.borrow_mut().set_slot_field(index, typ, val)
|
||||
})
|
||||
.try_collect()
|
||||
}
|
||||
|
||||
@@ -711,7 +735,10 @@ impl VM {
|
||||
val: f64,
|
||||
) -> Result<(), ICError> {
|
||||
self.batch_device(source, prefab, Some(name))
|
||||
.map(|device| device.borrow_mut().set_field(typ, val))
|
||||
.map(|device| {
|
||||
self.set_modified(device.borrow().id);
|
||||
device.borrow_mut().set_field(typ, val)
|
||||
})
|
||||
.try_collect()
|
||||
}
|
||||
|
||||
|
||||
@@ -79,14 +79,13 @@ impl DeviceRef {
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().ip)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "instructionCount")]
|
||||
@@ -95,14 +94,13 @@ impl DeviceRef {
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().ic)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "stack")]
|
||||
@@ -111,14 +109,13 @@ impl DeviceRef {
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| Stack(ic.as_ref().borrow().stack))
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "registers")]
|
||||
@@ -127,14 +124,13 @@ impl DeviceRef {
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| Registers(ic.as_ref().borrow().registers))
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "aliases", skip_typescript)]
|
||||
@@ -145,14 +141,13 @@ impl DeviceRef {
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().aliases.clone())
|
||||
})
|
||||
.flatten(),
|
||||
}),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
@@ -165,14 +160,13 @@ impl DeviceRef {
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().defines.clone())
|
||||
})
|
||||
.flatten(),
|
||||
}),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
@@ -185,14 +179,13 @@ impl DeviceRef {
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().pins)
|
||||
})
|
||||
.flatten(),
|
||||
}),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
@@ -203,18 +196,17 @@ impl DeviceRef {
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.borrow().state.clone())
|
||||
})
|
||||
.flatten()
|
||||
.map(|state| state.to_string())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "program")]
|
||||
#[wasm_bindgen(getter, js_name = "program", skip_typescript)]
|
||||
pub fn ic_program(&self) -> JsValue {
|
||||
serde_wasm_bindgen::to_value(
|
||||
&self
|
||||
@@ -222,14 +214,13 @@ impl DeviceRef {
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.map(|ic| {
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.borrow().program.clone())
|
||||
})
|
||||
.flatten(),
|
||||
}),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
@@ -306,6 +297,7 @@ impl DeviceRef {
|
||||
pub struct VM {
|
||||
vm: Rc<RefCell<ic10emu::VM>>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl VM {
|
||||
#[wasm_bindgen(constructor)]
|
||||
@@ -372,6 +364,18 @@ impl VM {
|
||||
pub fn ics(&self) -> Vec<u16> {
|
||||
self.vm.borrow().ics.keys().copied().collect_vec()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "lastOperationModified")]
|
||||
pub fn last_operation_modified(&self) -> Vec<u16> {
|
||||
self.vm.borrow().last_operation_modified()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Default for VM {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
||||
@@ -1,36 +1,120 @@
|
||||
type FieldType = 'Read' | 'Write' | 'ReadWrite';
|
||||
export type FieldType = "Read" | "Write" | "ReadWrite";
|
||||
|
||||
interface LogicField {
|
||||
field_type: FieldType,
|
||||
value: number,
|
||||
export interface LogicField {
|
||||
field_type: FieldType;
|
||||
value: number;
|
||||
}
|
||||
type Fields = Map<string, LogicField>;
|
||||
export type Fields = Map<string, LogicField>;
|
||||
|
||||
type SlotType = 'AccessCard' | 'Appliance' | 'Back' | 'Battery' | 'Blocked' | 'Bottle' | 'Cartridge' | 'Circuitboard' | 'CreditCard' | 'DataDisk' | 'DrillHead' | 'Egg' | 'Entity' | 'Flare' | 'GasCanister' | 'GasFilter' | 'Helmet' | 'Ingot' | 'LiquidBottle' | 'LiquidCanister' | 'Magazine' | 'Ore' | 'Organ' | 'Plant' | 'ProgramableChip' | 'ScanningHead' | 'SensorProcessingUnit' | 'SoundCartridge' | 'Suit' | 'Tool' | 'Torpedo' | 'None';
|
||||
|
||||
interface Slot {
|
||||
typ: SlotType,
|
||||
fields: Fields,
|
||||
export type SlotType =
|
||||
| "AccessCard"
|
||||
| "Appliance"
|
||||
| "Back"
|
||||
| "Battery"
|
||||
| "Blocked"
|
||||
| "Bottle"
|
||||
| "Cartridge"
|
||||
| "Circuitboard"
|
||||
| "CreditCard"
|
||||
| "DataDisk"
|
||||
| "DrillHead"
|
||||
| "Egg"
|
||||
| "Entity"
|
||||
| "Flare"
|
||||
| "GasCanister"
|
||||
| "GasFilter"
|
||||
| "Helmet"
|
||||
| "Ingot"
|
||||
| "LiquidBottle"
|
||||
| "LiquidCanister"
|
||||
| "Magazine"
|
||||
| "Ore"
|
||||
| "Organ"
|
||||
| "Plant"
|
||||
| "ProgramableChip"
|
||||
| "ScanningHead"
|
||||
| "SensorProcessingUnit"
|
||||
| "SoundCartridge"
|
||||
| "Suit"
|
||||
| "Tool"
|
||||
| "Torpedo"
|
||||
| "None";
|
||||
|
||||
export interface Slot {
|
||||
typ: SlotType;
|
||||
fields: Fields;
|
||||
}
|
||||
|
||||
type Reagents = Map<string, Map<number, number>>;
|
||||
export type Reagents = Map<string, Map<number, number>>;
|
||||
|
||||
type Connection = { CableNetwork: number } | 'Other' ;
|
||||
export type Connection = { CableNetwork: number } | "Other";
|
||||
|
||||
type Alias = { RegisterSpec: {indirection: number, target: number} } | { DeviceSpec: { device: "Db" | { Numbered: number } | { Indirect: { indirection: number, target: number } } }, connection: number | undefined };
|
||||
export type Alias =
|
||||
| { RegisterSpec: { indirection: number; target: number } }
|
||||
| {
|
||||
DeviceSpec: {
|
||||
device:
|
||||
| "Db"
|
||||
| { Numbered: number }
|
||||
| { Indirect: { indirection: number; target: number } };
|
||||
};
|
||||
connection: number | undefined;
|
||||
};
|
||||
|
||||
type Aliases = Map<string, Alias>;
|
||||
export type Aliases = Map<string, Alias>;
|
||||
|
||||
type Defines = Map<string, number>;
|
||||
export type Defines = Map<string, number>;
|
||||
|
||||
type Pins = (number | undefined)[]
|
||||
export type Pins = (number | undefined)[];
|
||||
|
||||
export type Operand =
|
||||
| { RegisterSpec: { indirection: number; target: number } }
|
||||
| {
|
||||
DeviceSpec: {
|
||||
device:
|
||||
| "Db"
|
||||
| { Numbered: number }
|
||||
| { Indirect: { indirection: number; target: number } };
|
||||
};
|
||||
connection: number | undefined;
|
||||
}
|
||||
| {
|
||||
Number:
|
||||
| { Float: number }
|
||||
| { Binary: number }
|
||||
| { Hexadecimal: number }
|
||||
| { Constant: number }
|
||||
| { String: string }
|
||||
| { Enum: number };
|
||||
}
|
||||
| { LogicType: string }
|
||||
| { SlotLogicType: string }
|
||||
| { BatchMode: string }
|
||||
| { ReagentMode: string }
|
||||
| { Identifier: { name: string } };
|
||||
|
||||
export interface Instruction {
|
||||
instruction: string;
|
||||
operands: Operand[];
|
||||
}
|
||||
|
||||
export type ICError = {
|
||||
ParseError: { line: number; start: number; end: number; msg: string };
|
||||
};
|
||||
|
||||
export interface Program {
|
||||
instructions: Instruction[];
|
||||
errors: ICError[];
|
||||
labels: Map<string, number>;
|
||||
}
|
||||
|
||||
export interface DeviceRef {
|
||||
readonly fields: Fields;
|
||||
readonly slots: Slot[];
|
||||
readonly reagents: Reagents;
|
||||
readonly connections: Connection[];
|
||||
readonly aliases?: Aliases | undefined;
|
||||
readonly defines?: Defines | undefined;
|
||||
readonly pins?: Pins;
|
||||
readonly fields: Fields;
|
||||
readonly slots: Slot[];
|
||||
readonly reagents: Reagents;
|
||||
readonly connections: Connection[];
|
||||
readonly aliases?: Aliases | undefined;
|
||||
readonly defines?: Defines | undefined;
|
||||
readonly pins?: Pins;
|
||||
readonly program?: Program;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ import "./nav";
|
||||
import "./share";
|
||||
import { ShareSessionDialog } from "./share";
|
||||
|
||||
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js';
|
||||
import { setBasePath } from "@shoelace-style/shoelace/dist/utilities/base-path.js";
|
||||
|
||||
// Set the base path to the folder you copied Shoelace's assets to
|
||||
setBasePath('/shoelace');
|
||||
setBasePath("/shoelace");
|
||||
|
||||
import "@shoelace-style/shoelace/dist/components/split-panel/split-panel.js";
|
||||
|
||||
@@ -18,6 +18,8 @@ import { Session } from "../session";
|
||||
import { VirtualMachine } from "../virtual_machine";
|
||||
import { openFile, saveFile } from "../utils";
|
||||
|
||||
import "../virtual_machine/ui";
|
||||
|
||||
@customElement("ic10emu-app")
|
||||
export class App extends BaseElement {
|
||||
static styles = [
|
||||
@@ -34,11 +36,7 @@ export class App extends BaseElement {
|
||||
}
|
||||
.app-body {
|
||||
flex-grow: 1;
|
||||
// z-index: auto;
|
||||
}
|
||||
// .z-fix {
|
||||
// z-index: 900;
|
||||
// }
|
||||
sl-split-panel {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -47,8 +45,8 @@ export class App extends BaseElement {
|
||||
|
||||
editorSettings: { fontSize: number; relativeLineNumbers: boolean };
|
||||
|
||||
@query('ace-ic10') accessor editor: IC10Editor;
|
||||
@query('session-share-dialog') accessor shareDialog: ShareSessionDialog;
|
||||
@query("ace-ic10") accessor editor: IC10Editor;
|
||||
@query("session-share-dialog") accessor shareDialog: ShareSessionDialog;
|
||||
|
||||
// get editor() {
|
||||
// return this.renderRoot.querySelector("ace-ic10") as IC10Editor;
|
||||
@@ -62,14 +60,13 @@ export class App extends BaseElement {
|
||||
window.App = this;
|
||||
this.session = new Session();
|
||||
this.vm = new VirtualMachine();
|
||||
|
||||
}
|
||||
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment {
|
||||
const root = super.createRenderRoot();
|
||||
root.addEventListener('app-share-session', this._handleShare.bind(this));
|
||||
root.addEventListener('app-open-file', this._handleOpenFile.bind(this));
|
||||
root.addEventListener('app-save-as', this._handleSaveAs.bind(this));
|
||||
root.addEventListener("app-share-session", this._handleShare.bind(this));
|
||||
root.addEventListener("app-open-file", this._handleOpenFile.bind(this));
|
||||
root.addEventListener("app-save-as", this._handleSaveAs.bind(this));
|
||||
return root;
|
||||
}
|
||||
|
||||
@@ -85,7 +82,7 @@ export class App extends BaseElement {
|
||||
snap-threshold="15"
|
||||
>
|
||||
<ace-ic10 slot="start" style=""></ace-ic10>
|
||||
<div slot="end">Controls</div>
|
||||
<div slot="end"><vm-ui></vm-ui></div>
|
||||
</sl-split-panel>
|
||||
</div>
|
||||
<session-share-dialog></session-share-dialog>
|
||||
@@ -93,8 +90,7 @@ export class App extends BaseElement {
|
||||
`;
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
}
|
||||
firstUpdated(): void {}
|
||||
|
||||
_handleShare(_e: Event) {
|
||||
// TODO:
|
||||
@@ -109,7 +105,6 @@ export class App extends BaseElement {
|
||||
_handleOpenFile(_e: Event) {
|
||||
openFile(window.Editor.editor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -29,6 +29,14 @@ export const defaultCss = [
|
||||
.mt-auto {
|
||||
margin-top: auto !important;
|
||||
}
|
||||
.hstack {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.vstack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ export async function setupLspWorker() {
|
||||
return worker;
|
||||
}
|
||||
|
||||
export import Ace = ace.Ace;
|
||||
export import EditSession = ace.Ace.EditSession;
|
||||
export import Editor = ace.Ace.Editor;
|
||||
import { Range } from "ace-builds";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
ace,
|
||||
Ace,
|
||||
Editor,
|
||||
EditSession,
|
||||
Range,
|
||||
@@ -32,8 +33,8 @@ import { html } from "lit";
|
||||
import { Ref, createRef, ref } from "lit/directives/ref.js";
|
||||
import { customElement, property, query } from "lit/decorators.js";
|
||||
import { editorStyles } from "./styles";
|
||||
import "./shortcuts-ui";
|
||||
import { AceKeyboardShortcuts } from "./shortcuts-ui";
|
||||
import "./shortcuts_ui";
|
||||
import { AceKeyboardShortcuts } from "./shortcuts_ui";
|
||||
|
||||
@customElement("ace-ic10")
|
||||
export class IC10Editor extends BaseElement {
|
||||
@@ -71,9 +72,9 @@ export class IC10Editor extends BaseElement {
|
||||
stylesAdded: string[];
|
||||
tooltipObserver: MutationObserver;
|
||||
|
||||
@query('.e-kb-shortcuts') accessor kbShortcuts: AceKeyboardShortcuts;
|
||||
@query(".e-kb-shortcuts") accessor kbShortcuts: AceKeyboardShortcuts;
|
||||
|
||||
@query('.e-settings-dialog') accessor settingDialog: SlDialog;
|
||||
@query(".e-settings-dialog") accessor settingDialog: SlDialog;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -294,31 +295,30 @@ export class IC10Editor extends BaseElement {
|
||||
window.App!.session.loadFromFragment();
|
||||
|
||||
window.App!.session.onActiveLine(((e: CustomEvent) => {
|
||||
const session = e.detail;
|
||||
for (const id of session.programs.keys()) {
|
||||
const active_line = session.getActiveLine(id);
|
||||
if (typeof active_line !== "undefined") {
|
||||
const marker = that.active_line_markers.get(id);
|
||||
if (marker) {
|
||||
that.sessions.get(id)?.removeMarker(marker);
|
||||
that.active_line_markers.set(id, null);
|
||||
}
|
||||
const session = that.sessions.get(id);
|
||||
if (session) {
|
||||
that.active_line_markers.set(
|
||||
id,
|
||||
session.addMarker(
|
||||
new Range(active_line, 0, active_line, 1),
|
||||
"vm_ic_active_line",
|
||||
"fullLine",
|
||||
true,
|
||||
),
|
||||
);
|
||||
if (that.active_session == id) {
|
||||
// editor.resize(true);
|
||||
// TODO: Scroll to line if vm was stepped
|
||||
//that.editor.scrollToLine(active_line, true, true, ()=>{})
|
||||
}
|
||||
const session = window.App?.session!;
|
||||
const id = e.detail;
|
||||
const active_line = session.getActiveLine(id);
|
||||
if (typeof active_line !== "undefined") {
|
||||
const marker = that.active_line_markers.get(id);
|
||||
if (marker) {
|
||||
that.sessions.get(id)?.removeMarker(marker);
|
||||
that.active_line_markers.set(id, null);
|
||||
}
|
||||
const session = that.sessions.get(id);
|
||||
if (session) {
|
||||
that.active_line_markers.set(
|
||||
id,
|
||||
session.addMarker(
|
||||
new Range(active_line, 0, active_line, 1),
|
||||
"vm_ic_active_line",
|
||||
"fullLine",
|
||||
true,
|
||||
),
|
||||
);
|
||||
if (that.active_session == id) {
|
||||
// editor.resize(true);
|
||||
// TODO: Scroll to line if vm was stepped
|
||||
//that.editor.scrollToLine(active_line, true, true, ()=>{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,6 +316,11 @@ export const editorStyles = css`
|
||||
background: rgba(76, 87, 103, 0.19);
|
||||
}
|
||||
|
||||
.vm_ic_active_line {
|
||||
position: absolute;
|
||||
background: rgba(121, 82, 179, 0.4);
|
||||
z-index: 20;
|
||||
}
|
||||
/* ----------------------
|
||||
* Editor Setting dialog
|
||||
* ---------------------- */
|
||||
|
||||
@@ -60,17 +60,21 @@ j ra
|
||||
|
||||
`;
|
||||
|
||||
import type { ICError } from "ic10emu_wasm";
|
||||
|
||||
export class Session extends EventTarget {
|
||||
_programs: Map<number, string>;
|
||||
_activeSession: number;
|
||||
_errors: Map<number, ICError[]>;
|
||||
_activeIC: number;
|
||||
_activeLines: Map<number, number>;
|
||||
_activeLine: number;
|
||||
_save_timeout?: ReturnType<typeof setTimeout>;
|
||||
constructor() {
|
||||
super();
|
||||
this._programs = new Map();
|
||||
this._errors = new Map();
|
||||
this._save_timeout = undefined;
|
||||
this._activeSession = 0;
|
||||
this._activeIC = 0;
|
||||
this._activeLines = new Map();
|
||||
this.loadFromFragment();
|
||||
|
||||
@@ -86,10 +90,26 @@ export class Session extends EventTarget {
|
||||
|
||||
set programs(programs) {
|
||||
this._programs = new Map([...programs]);
|
||||
this._fireOnLoad();
|
||||
}
|
||||
|
||||
get activeSession() {
|
||||
return this._activeSession;
|
||||
get activeIC() {
|
||||
return this._activeIC;
|
||||
}
|
||||
|
||||
set activeIC(val: number) {
|
||||
this._activeIC = val;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("session-active-ic", { detail: this.activeIC }),
|
||||
);
|
||||
}
|
||||
|
||||
onActiveIc(callback: EventListenerOrEventListenerObject) {
|
||||
this.addEventListener("session-active-ic", callback);
|
||||
}
|
||||
|
||||
get errors() {
|
||||
return this._errors;
|
||||
}
|
||||
|
||||
getActiveLine(id: number) {
|
||||
@@ -98,7 +118,7 @@ export class Session extends EventTarget {
|
||||
|
||||
setActiveLine(id: number, line: number) {
|
||||
this._activeLines.set(id, line);
|
||||
this._fireOnActiveLine();
|
||||
this._fireOnActiveLine(id);
|
||||
}
|
||||
|
||||
set activeLine(line: number) {
|
||||
@@ -110,6 +130,23 @@ export class Session extends EventTarget {
|
||||
this.save();
|
||||
}
|
||||
|
||||
setProgramErrors(id: number, errors: ICError[]) {
|
||||
this._errors.set(id, errors);
|
||||
this._fireOnErrors([id]);
|
||||
}
|
||||
|
||||
_fireOnErrors(ids: number[]) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("session-errors", {
|
||||
detail: ids,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
onErrors(callback: EventListenerOrEventListenerObject) {
|
||||
this.addEventListener("session-errors", callback);
|
||||
}
|
||||
|
||||
onLoad(callback: EventListenerOrEventListenerObject) {
|
||||
this.addEventListener("session-load", callback);
|
||||
}
|
||||
@@ -126,10 +163,10 @@ export class Session extends EventTarget {
|
||||
this.addEventListener("active-line", callback);
|
||||
}
|
||||
|
||||
_fireOnActiveLine() {
|
||||
_fireOnActiveLine(id: number) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("activeLine", {
|
||||
detail: this,
|
||||
new CustomEvent("active-line", {
|
||||
detail: id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -269,4 +306,3 @@ async function decompress(bytes: ArrayBuffer) {
|
||||
}
|
||||
return await concatUintArrays(chunks);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Ace } from "ace-builds";
|
||||
|
||||
function docReady(fn: () => void) {
|
||||
export function docReady(fn: () => void) {
|
||||
// see if DOM is already available
|
||||
if (
|
||||
document.readyState === "complete" ||
|
||||
@@ -12,8 +12,44 @@ function docReady(fn: () => void) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function replacer(key: any, value: any) {
|
||||
if(value instanceof Map) {
|
||||
return {
|
||||
dataType: 'Map',
|
||||
value: Array.from(value.entries()), // or with spread: value: [...value]
|
||||
};
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function reviver(_key: any, value: any) {
|
||||
if(typeof value === 'object' && value !== null) {
|
||||
if (value.dataType === 'Map') {
|
||||
return new Map(value.value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function toJson(value: any): string {
|
||||
return JSON.stringify(value, replacer);
|
||||
}
|
||||
|
||||
export function fromJson(value: string): any {
|
||||
return JSON.parse(value, reviver)
|
||||
}
|
||||
|
||||
export function structuralEqual(a: any, b: any): boolean {
|
||||
const _a = JSON.stringify(a, replacer);
|
||||
const _b = JSON.stringify(b, replacer);
|
||||
return _a === _b;
|
||||
|
||||
}
|
||||
|
||||
// probably not needed, fetch() exists now
|
||||
function makeRequest(opts: {
|
||||
export function makeRequest(opts: {
|
||||
method: string;
|
||||
url: string;
|
||||
headers: { [key: string]: string };
|
||||
@@ -57,7 +93,7 @@ function makeRequest(opts: {
|
||||
});
|
||||
}
|
||||
|
||||
async function saveFile(content: BlobPart) {
|
||||
export async function saveFile(content: BlobPart) {
|
||||
const blob = new Blob([content], { type: "text/plain" });
|
||||
if (typeof window.showSaveFilePicker !== "undefined") {
|
||||
console.log("Saving via FileSystem API");
|
||||
@@ -88,7 +124,7 @@ async function saveFile(content: BlobPart) {
|
||||
}
|
||||
}
|
||||
|
||||
async function openFile(editor: Ace.Editor) {
|
||||
export async function openFile(editor: Ace.Editor) {
|
||||
if (typeof window.showOpenFilePicker !== "undefined") {
|
||||
console.log("opening file via FileSystem Api");
|
||||
try {
|
||||
@@ -121,4 +157,3 @@ async function openFile(editor: Ace.Editor) {
|
||||
input.click();
|
||||
}
|
||||
}
|
||||
export { docReady, makeRequest, saveFile, openFile };
|
||||
|
||||
78
www/src/js/virtual_machine/base_device.ts
Normal file
78
www/src/js/virtual_machine/base_device.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { BaseElement } from "../components";
|
||||
import {
|
||||
DeviceRef,
|
||||
Fields,
|
||||
Reagents,
|
||||
Slot,
|
||||
Connection,
|
||||
} from "ic10emu_wasm";
|
||||
import { structuralEqual } from "../utils";
|
||||
|
||||
export class VMBaseDevice extends BaseElement {
|
||||
@property({ type: Number }) accessor deviceID: number;
|
||||
@state() protected accessor device: DeviceRef;
|
||||
|
||||
@state() accessor name: string | null;
|
||||
@state() accessor nameHash: number | null;
|
||||
@state() accessor prefabName: string | null;
|
||||
@state() accessor fields: Fields;
|
||||
@state() accessor slots: Slot[];
|
||||
@state() accessor reagents: Reagents;
|
||||
@state() accessor connections: Connection[];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.name = null;
|
||||
this.nameHash = null;
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
const root = super.connectedCallback();
|
||||
this.device = window.VM!.devices.get(this.deviceID)!;
|
||||
window.VM?.addEventListener(
|
||||
"vm-device-modified",
|
||||
this._handleDeviceModified.bind(this),
|
||||
);
|
||||
this.updateDevice();
|
||||
return root;
|
||||
}
|
||||
|
||||
_handleDeviceModified(e: CustomEvent) {
|
||||
const id = e.detail;
|
||||
if (this.deviceID === id) {
|
||||
this.updateDevice();
|
||||
}
|
||||
}
|
||||
|
||||
updateDevice() {
|
||||
const name = this.device.name ?? null;
|
||||
if (this.name !== name) {
|
||||
this.name = name;
|
||||
}
|
||||
const nameHash = this.device.nameHash ?? null;
|
||||
if (this.nameHash !== nameHash) {
|
||||
this.nameHash = nameHash;
|
||||
}
|
||||
const prefabName = this.device.prefabName ?? null;
|
||||
if (this.prefabName !== prefabName) {
|
||||
this.prefabName = prefabName;
|
||||
}
|
||||
const fields = this.device.fields;
|
||||
if (!structuralEqual(this.fields, fields)) {
|
||||
this.fields = fields;
|
||||
}
|
||||
const slots = this.device.slots;
|
||||
if (!structuralEqual(this.slots, slots)) {
|
||||
this.slots = slots;
|
||||
}
|
||||
const reagents = this.device.reagents;
|
||||
if (!structuralEqual(this.reagents, reagents)) {
|
||||
this.reagents = reagents;
|
||||
}
|
||||
const connections = this.device.connections;
|
||||
if (!structuralEqual(this.connections, connections)) {
|
||||
this.connections = connections;
|
||||
}
|
||||
}
|
||||
}
|
||||
190
www/src/js/virtual_machine/controls.ts
Normal file
190
www/src/js/virtual_machine/controls.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import { html, css } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import { defaultCss } from "../components";
|
||||
import { DeviceRef, ICError } from "ic10emu_wasm";
|
||||
import { VMBaseDevice } from "./base_device";
|
||||
import { structuralEqual } from "../utils";
|
||||
|
||||
import "@shoelace-style/shoelace/dist/components/card/card.js";
|
||||
import "@shoelace-style/shoelace/dist/components/button-group/button-group.js";
|
||||
import "@shoelace-style/shoelace/dist/components/button/button.js";
|
||||
import "@shoelace-style/shoelace/dist/components/icon/icon.js";
|
||||
import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js";
|
||||
import "@shoelace-style/shoelace/dist/components/divider/divider.js";
|
||||
|
||||
@customElement("vm-ic-controls")
|
||||
export class VMICControls extends VMBaseDevice {
|
||||
@state() accessor icIP: number;
|
||||
@state() accessor icOpCount: number;
|
||||
@state() accessor icState: string;
|
||||
@state() accessor errors: ICError[];
|
||||
|
||||
static styles = [
|
||||
...defaultCss,
|
||||
css`
|
||||
:host {
|
||||
}
|
||||
.card {
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: var(--sl-font-size-small);
|
||||
}
|
||||
.stats {
|
||||
font-size: var(--sl-font-size-x-small);
|
||||
}
|
||||
.device-id {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
.button-group-toolbar sl-button-group:not(:last-of-type) {
|
||||
margin-right: var(--sl-spacing-x-small);
|
||||
}
|
||||
sl-divider {
|
||||
--spacing: 0.25rem;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.deviceID = window.App!.session.activeIC;
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
const root = super.connectedCallback();
|
||||
window.VM?.addEventListener(
|
||||
"vm-run-ic",
|
||||
this._handleDeviceModified.bind(this),
|
||||
);
|
||||
window.App?.session.addEventListener(
|
||||
"session-active-ic",
|
||||
this._handleActiveIC.bind(this),
|
||||
);
|
||||
this.updateIC();
|
||||
return root;
|
||||
}
|
||||
|
||||
_handleActiveIC(e: CustomEvent) {
|
||||
const id = e.detail;
|
||||
if (this.deviceID !== id) {
|
||||
this.deviceID = id;
|
||||
this.device = window.VM!.devices.get(this.deviceID)!;
|
||||
}
|
||||
this.updateDevice();
|
||||
}
|
||||
|
||||
updateIC() {
|
||||
const ip = this.device.ip!;
|
||||
if (this.icIP !== ip) {
|
||||
this.icIP = ip;
|
||||
}
|
||||
const opCount = this.device.instructionCount!;
|
||||
if (this.icOpCount !== opCount) {
|
||||
this.icOpCount = opCount;
|
||||
}
|
||||
const state = this.device.state!;
|
||||
if (this.icState !== state) {
|
||||
this.icState = state;
|
||||
}
|
||||
const errors = this.device.program!.errors;
|
||||
if (!structuralEqual(this.errors, errors)) {
|
||||
this.errors = errors;
|
||||
}
|
||||
}
|
||||
|
||||
updateDevice(): void {
|
||||
super.updateDevice();
|
||||
this.updateIC();
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<sl-card class="card">
|
||||
<div class="controls" slot="header">
|
||||
<sl-button-group>
|
||||
<sl-tooltip
|
||||
content="Run the active IC through one tick (128 operations)"
|
||||
>
|
||||
<sl-button size="small" variant="primary" @click=${this._handleRunClick}>
|
||||
<span>Run</span>
|
||||
<sl-icon name="play" label="Run" slot="prefix"></sl-icon>
|
||||
</sl-button>
|
||||
</sl-tooltip>
|
||||
<sl-tooltip content="Run the active IC through a single operations">
|
||||
<sl-button size="small" variant="success" @click=${this._handleStepClick}>
|
||||
<span>Step</span>
|
||||
<sl-icon
|
||||
name="chevron-bar-right"
|
||||
label="Step"
|
||||
slot="prefix"
|
||||
></sl-icon>
|
||||
</sl-button>
|
||||
</sl-tooltip>
|
||||
<sl-tooltip content="Reset the active IC">
|
||||
<sl-button size="small" variant="warning" @click=${this._handleResetClick}>
|
||||
<span>Reset</span>
|
||||
<sl-icon
|
||||
name="arrow-clockwise"
|
||||
label="Reset"
|
||||
slot="prefix"
|
||||
></sl-icon>
|
||||
</sl-button>
|
||||
</sl-tooltip>
|
||||
</sl-button-group>
|
||||
<div class="device-id">
|
||||
Device:
|
||||
${this.deviceID}${this.name ?? this.prefabName
|
||||
? ` : ${this.name ?? this.prefabName}`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div class="hstack">
|
||||
<span>Instruction Pointer</span>
|
||||
<span class="ms-auto">${this.icIP}</span>
|
||||
</div>
|
||||
<sl-divider></sl-divider>
|
||||
<div class="hstack">
|
||||
<span>Last Run Operations Count</span>
|
||||
<span class="ms-auto">${this.icOpCount}</span>
|
||||
</div>
|
||||
<sl-divider></sl-divider>
|
||||
<div class="hstack">
|
||||
<span>Last State</span>
|
||||
<span class="ms-auto">${this.icState}</span>
|
||||
</div>
|
||||
<sl-divider></sl-divider>
|
||||
<div class="vstack">
|
||||
<span>Errors</span>
|
||||
${this.errors.map(
|
||||
(err) =>
|
||||
html`<div class="hstack">
|
||||
<span>
|
||||
Line: ${err.ParseError.line} -
|
||||
${err.ParseError.start}:${err.ParseError.end}
|
||||
</span>
|
||||
<span class="ms-auto">${err.ParseError.msg}</span>
|
||||
</div>`,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</sl-card>
|
||||
`;
|
||||
}
|
||||
|
||||
_handleRunClick() {
|
||||
window.VM?.run();
|
||||
}
|
||||
_handleStepClick() {
|
||||
window.VM?.step()
|
||||
}
|
||||
_handleResetClick() {
|
||||
window.VM?.reset()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import { DeviceRef, VM, init } from "ic10emu_wasm";
|
||||
import { VMDeviceUI } from "./device";
|
||||
import { BaseElement } from "../components";
|
||||
// import { Card } from 'bootstrap';
|
||||
import "./base_device";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -26,7 +24,7 @@ type DeviceDB = {
|
||||
};
|
||||
};
|
||||
|
||||
class VirtualMachine {
|
||||
class VirtualMachine extends EventTarget {
|
||||
ic10vm: VM;
|
||||
ui: VirtualMachineUI;
|
||||
_devices: Map<number, DeviceRef>;
|
||||
@@ -34,12 +32,12 @@ class VirtualMachine {
|
||||
db: DeviceDB;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const vm = init();
|
||||
|
||||
window.VM = this;
|
||||
|
||||
this.ic10vm = vm;
|
||||
// this.ui = new VirtualMachineUI(this);
|
||||
|
||||
this._devices = new Map();
|
||||
this._ics = new Map();
|
||||
@@ -58,7 +56,7 @@ class VirtualMachine {
|
||||
}
|
||||
|
||||
get activeIC() {
|
||||
return this._ics.get(window.App!.session.activeSession);
|
||||
return this._ics.get(window.App!.session.activeIC);
|
||||
}
|
||||
|
||||
updateDevices() {
|
||||
@@ -98,7 +96,12 @@ class VirtualMachine {
|
||||
if (ic && prog) {
|
||||
console.time(`CompileProgram_${id}_${attempt}`);
|
||||
try {
|
||||
this.ics.get(id)!.setCode(progs.get(id)!);
|
||||
this.ics.get(id)!.setCodeInvalid(progs.get(id)!);
|
||||
const compiled = this.ics.get(id)?.program!;
|
||||
window.App?.session.setProgramErrors(id, compiled.errors);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-device-modified", { detail: id }),
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
@@ -117,6 +120,9 @@ class VirtualMachine {
|
||||
console.log(e);
|
||||
}
|
||||
this.update();
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-run-ic", { detail: this.activeIC!.id }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +135,9 @@ class VirtualMachine {
|
||||
console.log(e);
|
||||
}
|
||||
this.update();
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-run-ic", { detail: this.activeIC!.id }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,24 +151,36 @@ class VirtualMachine {
|
||||
|
||||
update() {
|
||||
this.updateDevices();
|
||||
this.ic10vm.lastOperationModified.forEach((id, _index, _modifiedIds) => {
|
||||
if (this.devices.has(id)) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-device-modified", { detail: id }),
|
||||
);
|
||||
}
|
||||
}, this);
|
||||
const ic = this.activeIC!;
|
||||
window.App!.session.setActiveLine(window.App!.session.activeSession, ic.ip!);
|
||||
// this.ui.update(ic);
|
||||
window.App!.session.setActiveLine(window.App!.session.activeIC, ic.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 }),
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
setStack(addr: number, val: number) {
|
||||
const ic = this.activeIC;
|
||||
const ic = this.activeIC!;
|
||||
try {
|
||||
ic!.setStack(addr, val);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-device-modified", { detail: ic.id }),
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
@@ -176,14 +197,12 @@ class VirtualMachineUI {
|
||||
state: VMStateUI;
|
||||
registers: VMRegistersUI;
|
||||
stack: VMStackUI;
|
||||
devices: VMDeviceUI;
|
||||
|
||||
constructor(vm: VirtualMachine) {
|
||||
this.vm = vm;
|
||||
this.state = new VMStateUI(this);
|
||||
this.registers = new VMRegistersUI(this);
|
||||
this.stack = new VMStackUI(this);
|
||||
this.devices = new VMDeviceUI(this);
|
||||
|
||||
const that = this;
|
||||
|
||||
@@ -214,7 +233,6 @@ class VirtualMachineUI {
|
||||
this.state.update(ic);
|
||||
this.registers.update(ic);
|
||||
this.stack.update(ic);
|
||||
this.devices.update(ic);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
17
www/src/js/virtual_machine/ui.ts
Normal file
17
www/src/js/virtual_machine/ui.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { HTMLTemplateResult, html, css } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators.js";
|
||||
import { BaseElement, defaultCss } from "../components";
|
||||
|
||||
import "./controls.ts";
|
||||
|
||||
@customElement("vm-ui")
|
||||
export class VMUI extends BaseElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`<vm-ic-controls>`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user