device component with event to update state

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
Rachel Powers
2024-04-07 22:07:12 -07:00
parent 5c7ff7c287
commit b609b2e94b
18 changed files with 656 additions and 153 deletions

View File

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

View File

@@ -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,

View File

@@ -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
}
}

View File

@@ -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()
}

View File

@@ -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]

View File

@@ -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;
}

View File

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

View File

@@ -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;
}
`,
];

View File

@@ -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";

View File

@@ -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, ()=>{})
}
}
}

View File

@@ -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
* ---------------------- */

View File

@@ -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);
}

View File

@@ -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 };

View 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;
}
}
}

View 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()
}
}

View File

@@ -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);
}
}

View 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>`;
}
}