refactor(vm, frontend): memory instructions, baseObject
- parse and map meory instructions - use FrozenObjectFull to propogate data out of VM to componates (far less wasm calls)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use std::{collections::BTreeMap, fmt::Display, rc::Rc, str::FromStr};
|
||||
use std::{collections::BTreeMap, rc::Rc, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
errors::TemplateError,
|
||||
errors::{ICError, TemplateError},
|
||||
interpreter::ICInfo,
|
||||
network::Connection,
|
||||
vm::{
|
||||
@@ -77,10 +77,13 @@ pub struct ObjectInfo {
|
||||
pub reagents: Option<BTreeMap<i32, f64>>,
|
||||
pub memory: Option<Vec<f64>>,
|
||||
pub logic_values: Option<BTreeMap<LogicType, f64>>,
|
||||
pub slot_logic_values: Option<BTreeMap<u32, BTreeMap<LogicSlotType, f64>>>,
|
||||
pub entity: Option<EntityInfo>,
|
||||
pub source_code: Option<String>,
|
||||
pub compile_errors: Option<Vec<ICError>>,
|
||||
pub circuit: Option<ICInfo>,
|
||||
pub socketed_ic: Option<ObjectID>,
|
||||
pub visible_devices: Option<Vec<ObjectID>>,
|
||||
}
|
||||
|
||||
impl From<&VMObject> for ObjectInfo {
|
||||
@@ -97,10 +100,13 @@ impl From<&VMObject> for ObjectInfo {
|
||||
reagents: None,
|
||||
memory: None,
|
||||
logic_values: None,
|
||||
slot_logic_values: None,
|
||||
entity: None,
|
||||
source_code: None,
|
||||
compile_errors: None,
|
||||
circuit: None,
|
||||
socketed_ic: None,
|
||||
visible_devices: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,10 +131,13 @@ impl ObjectInfo {
|
||||
reagents: None,
|
||||
memory: None,
|
||||
logic_values: None,
|
||||
slot_logic_values: None,
|
||||
entity: None,
|
||||
source_code: None,
|
||||
compile_errors: None,
|
||||
circuit: None,
|
||||
socketed_ic: None,
|
||||
visible_devices: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,6 +238,8 @@ impl ObjectInfo {
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
let visible_devices = device.get_vm().visible_devices(*device.get_id());
|
||||
self.visible_devices.replace(visible_devices);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -254,6 +265,29 @@ impl ObjectInfo {
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
let num_slots = logic.slots_count();
|
||||
if num_slots > 0 {
|
||||
let slot_logic_values = (0..num_slots)
|
||||
.map(|index| {
|
||||
(
|
||||
index as u32,
|
||||
LogicSlotType::iter()
|
||||
.filter_map(|slt| {
|
||||
if logic.can_slot_logic_read(slt, index as f64) {
|
||||
Some((
|
||||
slt,
|
||||
logic.get_slot_logic(slt, index as f64).unwrap_or(0.0),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
self.slot_logic_values.replace(slot_logic_values);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
@@ -282,6 +316,10 @@ impl ObjectInfo {
|
||||
if !code.is_empty() {
|
||||
self.source_code.replace(code);
|
||||
}
|
||||
let errors = source.get_compile_errors();
|
||||
if !errors.is_empty() {
|
||||
self.compile_errors.replace(errors);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
@@ -868,15 +906,10 @@ impl FrozenObject {
|
||||
.get_template(Prefab::Hash(obj_ref.get_prefab().hash))
|
||||
.map_or_else(
|
||||
|| try_template_from_interfaces(&interfaces, obj),
|
||||
|template| {
|
||||
Ok(template)
|
||||
},
|
||||
|template| Ok(template),
|
||||
)?;
|
||||
|
||||
Ok(FrozenObjectFull {
|
||||
obj_info,
|
||||
template,
|
||||
})
|
||||
Ok(FrozenObjectFull { obj_info, template })
|
||||
}
|
||||
|
||||
pub fn freeze_object_sparse(obj: &VMObject, vm: &Rc<VM>) -> Result<Self, TemplateError> {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -291,12 +291,36 @@ pub struct StructureInfo {
|
||||
pub small_grid: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "tsify", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
|
||||
pub enum InstructionPartType {
|
||||
Bool8,
|
||||
Byte8,
|
||||
Int32,
|
||||
UInt32,
|
||||
Short16,
|
||||
UShort16,
|
||||
Unused(u32),
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "tsify", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
|
||||
pub struct InstructionPart {
|
||||
pub range: (u32, u32),
|
||||
pub name: String,
|
||||
pub typ: InstructionPartType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "tsify", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
|
||||
pub struct Instruction {
|
||||
pub description: String,
|
||||
pub description_stripped: String,
|
||||
pub typ: String,
|
||||
pub value: i64,
|
||||
pub valid: (u32, Option<u32>),
|
||||
pub parts: Vec<InstructionPart>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -328,9 +328,23 @@ interface TypedEventTargetInterface<EventMap> extends EventTarget {
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void;
|
||||
|
||||
removeEventListener<K extends keyof EventMap>(
|
||||
type: K,
|
||||
callback: (
|
||||
event: EventMap[K] extends Event ? EventMap[K] : never,
|
||||
) => EventMap[K] extends Event ? void : never,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void;
|
||||
|
||||
addEventListener(
|
||||
type: string,
|
||||
callback: EventListenerOrEventListenerObject | null,
|
||||
options?: EventListenerOptions | boolean,
|
||||
): void;
|
||||
|
||||
removeEventListener(
|
||||
type: string,
|
||||
callback: EventListenerOrEventListenerObject | null,
|
||||
options?: EventListenerOptions | boolean,
|
||||
): void;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ import type {
|
||||
ObjectID,
|
||||
TemplateDatabase,
|
||||
FrozenObjectFull,
|
||||
Class,
|
||||
LogicSlotType,
|
||||
SlotOccupantInfo,
|
||||
ICState,
|
||||
} from "ic10emu_wasm";
|
||||
import { crc32, structuralEqual } from "utils";
|
||||
import { LitElement, PropertyValueMap } from "lit";
|
||||
@@ -24,20 +28,22 @@ export declare class VMObjectMixinInterface {
|
||||
nameHash: number | null;
|
||||
prefabName: string | null;
|
||||
prefabHash: number | null;
|
||||
fields: Map<LogicType, LogicField>;
|
||||
slots: Slot[];
|
||||
reagents: Map<number, number>;
|
||||
connections: Connection[];
|
||||
icIP: number;
|
||||
icOpCount: number;
|
||||
icState: string;
|
||||
errors: ICError[];
|
||||
logicFields: Map<LogicType, LogicField> | null;
|
||||
slots: VmObjectSlotInfo[] | null;
|
||||
slotsCount: number | null;
|
||||
reagents: Map<number, number> | null;
|
||||
connections: Connection[] | null;
|
||||
icIP: number | null;
|
||||
icOpCount: number | null;
|
||||
icState: string | null;
|
||||
errors: ICError[] | null;
|
||||
registers: number[] | null;
|
||||
memory: number[] | null;
|
||||
aliases: Map<string, Operand> | null;
|
||||
defines: Map<string, string> | null;
|
||||
defines: Map<string, number> | null;
|
||||
numPins: number | null;
|
||||
pins: Map<number, ObjectID> | null;
|
||||
visibleDevices: ObjectID[] | null;
|
||||
_handleDeviceModified(e: CustomEvent): void;
|
||||
updateDevice(): void;
|
||||
updateIC(): void;
|
||||
@@ -54,23 +60,34 @@ export type VMObjectMixinSubscription =
|
||||
| "slots-count"
|
||||
| "reagents"
|
||||
| "connections"
|
||||
| "memory"
|
||||
| "ic"
|
||||
| "active-ic"
|
||||
| { field: LogicType }
|
||||
| { slot: number }
|
||||
| "visible-devices";
|
||||
|
||||
export interface VmObjectSlotInfo {
|
||||
parent: ObjectID;
|
||||
index: number;
|
||||
name: string;
|
||||
typ: Class;
|
||||
logicFields: Map<LogicSlotType, LogicField>;
|
||||
quantity: number;
|
||||
occupant: FrozenObjectFull | undefined;
|
||||
}
|
||||
|
||||
export const VMObjectMixin = <T extends Constructor<LitElement>>(
|
||||
superClass: T,
|
||||
) => {
|
||||
class VMObjectMixinClass extends superClass {
|
||||
private _deviceID: number;
|
||||
get deviceID() {
|
||||
return this._deviceID;
|
||||
private _objectID: number;
|
||||
get objectID() {
|
||||
return this._objectID;
|
||||
}
|
||||
@property({ type: Number })
|
||||
set deviceID(val: number) {
|
||||
this._deviceID = val;
|
||||
set objectID(val: number) {
|
||||
this._objectID = val;
|
||||
this.updateDevice();
|
||||
}
|
||||
|
||||
@@ -93,40 +110,42 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
|
||||
|
||||
@state() name: string | null = null;
|
||||
@state() nameHash: number | null = null;
|
||||
@state() prefabName: string | null;
|
||||
@state() prefabHash: number | null;
|
||||
@state() fields: Map<LogicType, LogicField>;
|
||||
@state() slots: Slot[];
|
||||
@state() reagents: Map<number, number>;
|
||||
@state() connections: Connection[];
|
||||
@state() icIP: number;
|
||||
@state() icOpCount: number;
|
||||
@state() icState: string;
|
||||
@state() errors: ICError[];
|
||||
@state() registers: number[] | null;
|
||||
@state() memory: number[] | null;
|
||||
@state() aliases: Map<string, Operand> | null;
|
||||
@state() defines: Map<string, string> | null;
|
||||
@state() numPins: number | null;
|
||||
@state() pins: Map<number, ObjectID> | null;
|
||||
@state() prefabName: string | null = null;
|
||||
@state() prefabHash: number | null = null;
|
||||
@state() logicFields: Map<LogicType, LogicField> | null = null;
|
||||
@state() slots: VmObjectSlotInfo[] | null = null;
|
||||
@state() slotsCount: number | null = null;
|
||||
@state() reagents: Map<number, number> | null = null;
|
||||
@state() connections: Connection[] | null = null;
|
||||
@state() icIP: number | null = null;
|
||||
@state() icOpCount: number | null = null;
|
||||
@state() icState: ICState | null = null;
|
||||
@state() errors: ICError[] | null = null;
|
||||
@state() registers: number[] | null = null;
|
||||
@state() memory: number[] | null = null;
|
||||
@state() aliases: Map<string, Operand> | null = null;
|
||||
@state() defines: Map<string, number> | null = null;
|
||||
@state() numPins: number | null = null;
|
||||
@state() pins: Map<number, ObjectID> | null = null;
|
||||
@state() visibleDevices: ObjectID[] | null = null;
|
||||
|
||||
connectedCallback(): void {
|
||||
const root = super.connectedCallback();
|
||||
window.VM.get().then((vm) => {
|
||||
vm.addEventListener(
|
||||
"vm-device-modified",
|
||||
"vm-objects-modified",
|
||||
this._handleDeviceModified.bind(this),
|
||||
);
|
||||
vm.addEventListener(
|
||||
"vm-devices-update",
|
||||
"vm-objects-update",
|
||||
this._handleDevicesModified.bind(this),
|
||||
);
|
||||
vm.addEventListener(
|
||||
"vm-device-id-change",
|
||||
"vm-object-id-change",
|
||||
this._handleDeviceIdChange.bind(this),
|
||||
);
|
||||
vm.addEventListener(
|
||||
"vm-devices-removed",
|
||||
"vm-objects-removed",
|
||||
this._handleDevicesRemoved.bind(this),
|
||||
);
|
||||
});
|
||||
@@ -137,19 +156,19 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
|
||||
disconnectedCallback(): void {
|
||||
window.VM.get().then((vm) => {
|
||||
vm.removeEventListener(
|
||||
"vm-device-modified",
|
||||
"vm-objects-modified",
|
||||
this._handleDeviceModified.bind(this),
|
||||
);
|
||||
vm.removeEventListener(
|
||||
"vm-devices-update",
|
||||
"vm-objects-update",
|
||||
this._handleDevicesModified.bind(this),
|
||||
);
|
||||
vm.removeEventListener(
|
||||
"vm-device-id-change",
|
||||
"vm-object-id-change",
|
||||
this._handleDeviceIdChange.bind(this),
|
||||
);
|
||||
vm.removeEventListener(
|
||||
"vm-devices-removed",
|
||||
"vm-objects-removed",
|
||||
this._handleDevicesRemoved.bind(this),
|
||||
);
|
||||
});
|
||||
@@ -158,7 +177,7 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
|
||||
async _handleDeviceModified(e: CustomEvent) {
|
||||
const id = e.detail;
|
||||
const activeIcId = window.App.app.session.activeIC;
|
||||
if (this.deviceID === id) {
|
||||
if (this.objectID === id) {
|
||||
this.updateDevice();
|
||||
} else if (
|
||||
id === activeIcId &&
|
||||
@@ -168,7 +187,7 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
|
||||
this.requestUpdate();
|
||||
} else if (this.objectSubscriptions.includes("visible-devices")) {
|
||||
const visibleDevices = await window.VM.vm.visibleDeviceIds(
|
||||
this.deviceID,
|
||||
this.objectID,
|
||||
);
|
||||
if (visibleDevices.includes(id)) {
|
||||
this.updateDevice();
|
||||
@@ -180,7 +199,7 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
|
||||
async _handleDevicesModified(e: CustomEvent<number[]>) {
|
||||
const activeIcId = window.App.app.session.activeIC;
|
||||
const ids = e.detail;
|
||||
if (ids.includes(this.deviceID)) {
|
||||
if (ids.includes(this.objectID)) {
|
||||
this.updateDevice();
|
||||
if (this.objectSubscriptions.includes("visible-devices")) {
|
||||
this.requestUpdate();
|
||||
@@ -193,7 +212,7 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
|
||||
this.requestUpdate();
|
||||
} else if (this.objectSubscriptions.includes("visible-devices")) {
|
||||
const visibleDevices = await window.VM.vm.visibleDeviceIds(
|
||||
this.deviceID,
|
||||
this.objectID,
|
||||
);
|
||||
if (ids.some((id) => visibleDevices.includes(id))) {
|
||||
this.updateDevice();
|
||||
@@ -203,11 +222,11 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
|
||||
}
|
||||
|
||||
async _handleDeviceIdChange(e: CustomEvent<{ old: number; new: number }>) {
|
||||
if (this.deviceID === e.detail.old) {
|
||||
this.deviceID = e.detail.new;
|
||||
if (this.objectID === e.detail.old) {
|
||||
this.objectID = e.detail.new;
|
||||
} else if (this.objectSubscriptions.includes("visible-devices")) {
|
||||
const visibleDevices = await window.VM.vm.visibleDeviceIds(
|
||||
this.deviceID,
|
||||
this.objectID,
|
||||
);
|
||||
if (
|
||||
visibleDevices.some(
|
||||
@@ -227,48 +246,86 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
|
||||
}
|
||||
|
||||
updateDevice() {
|
||||
this.obj = window.VM.vm.objects.get(this.deviceID)!;
|
||||
this.obj = window.VM.vm.objects.get(this.objectID)!;
|
||||
|
||||
if (typeof this.obj === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
let newFields: Map<LogicType, LogicField> | null = null;
|
||||
if (
|
||||
this.objectSubscriptions.includes("slots") ||
|
||||
this.objectSubscriptions.includes("slots-count")
|
||||
this.objectSubscriptions.some(
|
||||
(sub) =>
|
||||
sub === "fields" || (typeof sub === "object" && "field" in sub),
|
||||
)
|
||||
) {
|
||||
const slotsOccupantInfo = this.obj.obj_info.slots;
|
||||
const logicValues =
|
||||
this.obj.obj_info.logic_values ?? new Map<LogicType, number>();
|
||||
const logicTemplate =
|
||||
"logic" in this.obj.template ? this.obj.template.logic : null;
|
||||
newFields = new Map(
|
||||
Array.from(logicTemplate?.logic_types.entries() ?? []).map(
|
||||
([lt, access]) => {
|
||||
let field: LogicField = {
|
||||
field_type: access,
|
||||
value: logicValues.get(lt) ?? 0,
|
||||
};
|
||||
return [lt, field];
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const visibleDevices = this.obj.obj_info.visible_devices ?? [];
|
||||
if (!structuralEqual(this.visibleDevices, visibleDevices)) {
|
||||
this.visibleDevices = visibleDevices;
|
||||
}
|
||||
|
||||
let newSlots: VmObjectSlotInfo[] | null = null;
|
||||
if (
|
||||
this.objectSubscriptions.some(
|
||||
(sub) =>
|
||||
sub === "slots" || (typeof sub === "object" && "slot" in sub),
|
||||
)
|
||||
) {
|
||||
const slotsOccupantInfo =
|
||||
this.obj.obj_info.slots ?? new Map<number, SlotOccupantInfo>();
|
||||
const slotsLogicValues =
|
||||
this.obj.obj_info.slot_logic_values ??
|
||||
new Map<number, Map<LogicSlotType, number>>();
|
||||
const logicTemplate =
|
||||
"logic" in this.obj.template ? this.obj.template.logic : null;
|
||||
const slotsTemplate =
|
||||
"slots" in this.obj.template ? this.obj.template.slots : [];
|
||||
let slots: Slot[] | null = null;
|
||||
if (slotsOccupantInfo.size !== 0) {
|
||||
slots = slotsTemplate.map((template, index) => {
|
||||
let slot = {
|
||||
parent: this.obj.obj_info.id,
|
||||
index: index,
|
||||
name: template.name,
|
||||
typ: template.typ,
|
||||
readable_logic: Array.from(
|
||||
logicTemplate?.logic_slot_types.get(index)?.entries() ?? [],
|
||||
)
|
||||
.filter(([_, val]) => val === "Read" || val === "ReadWrite")
|
||||
.map(([key, _]) => key),
|
||||
writeable_logic: Array.from(
|
||||
logicTemplate?.logic_slot_types.get(index)?.entries() ?? [],
|
||||
)
|
||||
.filter(([_, val]) => val === "Write" || val === "ReadWrite")
|
||||
.map(([key, _]) => key),
|
||||
occupant: slotsOccupantInfo.get(index),
|
||||
};
|
||||
return slot;
|
||||
});
|
||||
}
|
||||
|
||||
if (!structuralEqual(this.slots, slots)) {
|
||||
this.slots = slots;
|
||||
}
|
||||
newSlots = slotsTemplate.map((template, index) => {
|
||||
const fieldEntryInfos = Array.from(
|
||||
logicTemplate?.logic_slot_types.get(index).entries() ?? [],
|
||||
);
|
||||
const logicFields = new Map(
|
||||
fieldEntryInfos.map(([slt, access]) => {
|
||||
let field: LogicField = {
|
||||
field_type: access,
|
||||
value: slotsLogicValues.get(index)?.get(slt) ?? 0,
|
||||
};
|
||||
return [slt, field];
|
||||
}),
|
||||
);
|
||||
let occupantInfo = slotsOccupantInfo.get(index);
|
||||
let occupant =
|
||||
typeof occupantInfo !== "undefined"
|
||||
? window.VM.vm.objects.get(occupantInfo.id)
|
||||
: null;
|
||||
let slot: VmObjectSlotInfo = {
|
||||
parent: this.obj.obj_info.id,
|
||||
index: index,
|
||||
name: template.name,
|
||||
typ: template.typ,
|
||||
logicFields: logicFields,
|
||||
occupant: occupant,
|
||||
quantity: occupantInfo?.quantity ?? 0,
|
||||
};
|
||||
return slot;
|
||||
});
|
||||
}
|
||||
|
||||
for (const sub of this.objectSubscriptions) {
|
||||
@@ -293,22 +350,20 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
|
||||
this.prefabHash = crc32(prefabName);
|
||||
}
|
||||
} else if (sub === "fields") {
|
||||
const fields = this.obj.obj_info.logic_values ?? null;
|
||||
const logicTemplate =
|
||||
"logic" in this.obj.template ? this.obj.template.logic : null;
|
||||
let logic_fields: Map<LogicType, LogicField> | null = null;
|
||||
if (fields !== null) {
|
||||
logic_fields = new Map();
|
||||
for (const [lt, val] of fields) {
|
||||
const access = logicTemplate?.logic_types.get(lt) ?? "Read";
|
||||
logic_fields.set(lt, {
|
||||
value: val,
|
||||
field_type: access,
|
||||
});
|
||||
}
|
||||
if (!structuralEqual(this.logicFields, newFields)) {
|
||||
this.logicFields = newFields;
|
||||
}
|
||||
if (!structuralEqual(this.fields, logic_fields)) {
|
||||
this.fields = logic_fields;
|
||||
} else if (sub === "slots") {
|
||||
if (!structuralEqual(this.slots, newSlots)) {
|
||||
this.slots = newSlots;
|
||||
this.slotsCount = newSlots.length;
|
||||
}
|
||||
} else if (sub === "slots-count") {
|
||||
const slotsTemplate =
|
||||
"slots" in this.obj.template ? this.obj.template.slots : [];
|
||||
const slotsCount = slotsTemplate.length;
|
||||
if (this.slotsCount !== slotsCount) {
|
||||
this.slotsCount = slotsCount;
|
||||
}
|
||||
} else if (sub === "reagents") {
|
||||
const reagents = this.obj.obj_info.reagents;
|
||||
@@ -367,10 +422,15 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
|
||||
if (!structuralEqual(this.connections, connections)) {
|
||||
this.connections = connections;
|
||||
}
|
||||
} else if (sub === "memory") {
|
||||
const stack = this.obj.obj_info.memory ?? null;
|
||||
if (!structuralEqual(this.memory, stack)) {
|
||||
this.memory = stack;
|
||||
}
|
||||
} else if (sub === "ic") {
|
||||
if (
|
||||
typeof this.obj.obj_info.circuit !== "undefined" ||
|
||||
this.obj.obj_info.socketed_ic !== "undefined"
|
||||
typeof this.obj.obj_info.socketed_ic !== "undefined"
|
||||
) {
|
||||
this.updateIC();
|
||||
}
|
||||
@@ -382,21 +442,19 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
|
||||
}
|
||||
} else {
|
||||
if ("field" in sub) {
|
||||
const fields = this.obj.obj_info.logic_values;
|
||||
if (this.fields.get(sub.field) !== fields.get(sub.field)) {
|
||||
this.fields = fields;
|
||||
if (this.logicFields.get(sub.field) !== newFields.get(sub.field)) {
|
||||
this.logicFields = newFields;
|
||||
}
|
||||
} else if ("slot" in sub) {
|
||||
const slots = this.obj.slots;
|
||||
if (
|
||||
typeof this.slots === "undefined" ||
|
||||
this.slots.length < sub.slot
|
||||
) {
|
||||
this.slots = slots;
|
||||
this.slots = newSlots;
|
||||
} else if (
|
||||
!structuralEqual(this.slots[sub.slot], slots[sub.slot])
|
||||
!structuralEqual(this.slots[sub.slot], newSlots[sub.slot])
|
||||
) {
|
||||
this.slots = slots;
|
||||
this.slots = newSlots;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -404,41 +462,42 @@ export const VMObjectMixin = <T extends Constructor<LitElement>>(
|
||||
}
|
||||
|
||||
updateIC() {
|
||||
const ip = this.obj.ip!;
|
||||
const ip = this.obj.obj_info.circuit?.instruction_pointer ?? null;
|
||||
if (this.icIP !== ip) {
|
||||
this.icIP = ip;
|
||||
}
|
||||
const opCount = this.obj.instructionCount!;
|
||||
const opCount =
|
||||
this.obj.obj_info.circuit?.yield_instruciton_count ?? null;
|
||||
if (this.icOpCount !== opCount) {
|
||||
this.icOpCount = opCount;
|
||||
}
|
||||
const state = this.obj.state!;
|
||||
const state = this.obj.obj_info.circuit?.state ?? null;
|
||||
if (this.icState !== state) {
|
||||
this.icState = state;
|
||||
}
|
||||
const errors = this.obj.program?.errors ?? null;
|
||||
const errors = this.obj.obj_info.compile_errors ?? null;
|
||||
if (!structuralEqual(this.errors, errors)) {
|
||||
this.errors = errors;
|
||||
}
|
||||
const registers = this.obj.registers ?? null;
|
||||
const registers = this.obj.obj_info.circuit?.registers ?? null;
|
||||
if (!structuralEqual(this.registers, registers)) {
|
||||
this.registers = registers;
|
||||
}
|
||||
const stack = this.obj.stack ?? null;
|
||||
if (!structuralEqual(this.memory, stack)) {
|
||||
this.memory = stack;
|
||||
}
|
||||
const aliases = this.obj.aliases ?? null;
|
||||
const aliases = this.obj.obj_info.circuit?.aliases ?? null;
|
||||
if (!structuralEqual(this.aliases, aliases)) {
|
||||
this.aliases = aliases;
|
||||
}
|
||||
const defines = this.obj.defines ?? null;
|
||||
const defines = this.obj.obj_info.circuit?.defines ?? null;
|
||||
if (!structuralEqual(this.defines, defines)) {
|
||||
this.defines = defines;
|
||||
}
|
||||
const pins = this.obj.pins ?? null;
|
||||
const pins = this.obj.obj_info.device_pins ?? new Map<number, number>();
|
||||
if (!structuralEqual(this.pins, pins)) {
|
||||
this.pins = pins;
|
||||
this.numPins =
|
||||
"device" in this.obj.template
|
||||
? this.obj.template.device.device_pins_length
|
||||
: Math.max(...Array.from(this.pins?.keys() ?? [0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -492,16 +551,16 @@ export const VMActiveICMixin = <T extends Constructor<LitElement>>(
|
||||
return VMActiveICMixinClass as Constructor<VMObjectMixinInterface> & T;
|
||||
};
|
||||
|
||||
export declare class VMDeviceDBMixinInterface {
|
||||
export declare class VMTemplateDBMixinInterface {
|
||||
templateDB: TemplateDatabase;
|
||||
_handleDeviceDBLoad(e: CustomEvent): void;
|
||||
postDBSetUpdate(): void;
|
||||
}
|
||||
|
||||
export const VMDeviceDBMixin = <T extends Constructor<LitElement>>(
|
||||
export const VMTemplateDBMixin = <T extends Constructor<LitElement>>(
|
||||
superClass: T,
|
||||
) => {
|
||||
class VMDeviceDBMixinClass extends superClass {
|
||||
class VMTemplateDBMixinClass extends superClass {
|
||||
connectedCallback(): void {
|
||||
const root = super.connectedCallback();
|
||||
window.VM.vm.addEventListener(
|
||||
@@ -540,5 +599,5 @@ export const VMDeviceDBMixin = <T extends Constructor<LitElement>>(
|
||||
}
|
||||
}
|
||||
|
||||
return VMDeviceDBMixinClass as Constructor<VMDeviceDBMixinInterface> & T;
|
||||
return VMTemplateDBMixinClass as Constructor<VMTemplateDBMixinInterface> & T;
|
||||
};
|
||||
|
||||
@@ -12,11 +12,11 @@ import { cache } from "lit/directives/cache.js";
|
||||
import { default as uFuzzy } from "@leeoniya/ufuzzy";
|
||||
import { when } from "lit/directives/when.js";
|
||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
||||
import { VMDeviceDBMixin } from "virtual_machine/base_device";
|
||||
import { VMTemplateDBMixin } from "virtual_machine/base_device";
|
||||
|
||||
|
||||
@customElement("vm-add-device-button")
|
||||
export class VMAddDeviceButton extends VMDeviceDBMixin(BaseElement) {
|
||||
export class VMAddDeviceButton extends VMTemplateDBMixin(BaseElement) {
|
||||
static styles = [
|
||||
...defaultCss,
|
||||
css`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { html, css, HTMLTemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import { BaseElement, defaultCss } from "components";
|
||||
import { VMDeviceDBMixin, VMObjectMixin } from "virtual_machine/base_device";
|
||||
import { VMTemplateDBMixin, VMObjectMixin } from "virtual_machine/base_device";
|
||||
import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.component.js";
|
||||
import { parseIntWithHexOrBinary, parseNumber } from "utils";
|
||||
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
|
||||
@@ -15,7 +15,9 @@ import { repeat } from "lit/directives/repeat.js";
|
||||
export type CardTab = "fields" | "slots" | "reagents" | "networks" | "pins";
|
||||
|
||||
@customElement("vm-device-card")
|
||||
export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
|
||||
export class VMDeviceCard extends VMTemplateDBMixin(
|
||||
VMObjectMixin(BaseElement),
|
||||
) {
|
||||
image_err: boolean;
|
||||
|
||||
@property({ type: Boolean }) open: boolean;
|
||||
@@ -141,7 +143,17 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
|
||||
badges.push(html`<sl-badge variant="primary" pill pulse>db</sl-badge>`);
|
||||
}
|
||||
const activeIc = window.VM.vm.activeIC;
|
||||
activeIc?.pins?.forEach((id, index) => {
|
||||
|
||||
const numPins =
|
||||
"device" in activeIc?.template
|
||||
? activeIc.template.device.device_pins_length
|
||||
: Math.max(
|
||||
...Array.from(activeIc?.obj_info.device_pins?.keys() ?? [0]),
|
||||
);
|
||||
const pins = new Array(numPins)
|
||||
.fill(true)
|
||||
.map((_, index) => this.pins.get(index));
|
||||
pins.forEach((id, index) => {
|
||||
if (this.objectID == id) {
|
||||
badges.push(
|
||||
html`<sl-badge variant="success" pill>d${index}</sl-badge>`,
|
||||
@@ -150,31 +162,71 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
|
||||
}, this);
|
||||
return html`
|
||||
<sl-tooltip content="${this.prefabName}">
|
||||
<img class="image me-2" src="img/stationpedia/${this.prefabName}.png"
|
||||
onerror="this.src = '${VMDeviceCard.transparentImg}'" />
|
||||
<img
|
||||
class="image me-2"
|
||||
src="img/stationpedia/${this.prefabName}.png"
|
||||
onerror="this.src = '${VMDeviceCard.transparentImg}'"
|
||||
/>
|
||||
</sl-tooltip>
|
||||
<div class="header-name">
|
||||
<sl-input id="vmDeviceCard${this.objectID}Id" class="device-id me-1" size="small" pill value=${this.objectID}
|
||||
@sl-change=${this._handleChangeID}>
|
||||
<sl-input
|
||||
id="vmDeviceCard${this.objectID}Id"
|
||||
class="device-id me-1"
|
||||
size="small"
|
||||
pill
|
||||
value=${this.objectID.toString()}
|
||||
@sl-change=${this._handleChangeID}
|
||||
>
|
||||
<span slot="prefix">Id</span>
|
||||
<sl-copy-button slot="suffix" .value=${this.objectID}></sl-copy-button>
|
||||
<sl-copy-button
|
||||
slot="suffix"
|
||||
.value=${this.objectID.toString()}
|
||||
></sl-copy-button>
|
||||
</sl-input>
|
||||
<sl-input id="vmDeviceCard${this.objectID}Name" class="device-name me-1" size="small" pill
|
||||
placeholder=${this.prefabName} value=${this.name} @sl-change=${this._handleChangeName}>
|
||||
<sl-input
|
||||
id="vmDeviceCard${this.objectID}Name"
|
||||
class="device-name me-1"
|
||||
size="small"
|
||||
pill
|
||||
placeholder=${this.prefabName}
|
||||
value=${this.name}
|
||||
@sl-change=${this._handleChangeName}
|
||||
>
|
||||
<span slot="prefix">Name</span>
|
||||
<sl-copy-button slot="suffix" from="vmDeviceCard${this.objectID}Name.value"></sl-copy-button>
|
||||
<sl-copy-button
|
||||
slot="suffix"
|
||||
from="vmDeviceCard${this.objectID}Name.value"
|
||||
></sl-copy-button>
|
||||
</sl-input>
|
||||
<sl-input id="vmDeviceCard${this.objectID}NameHash" size="small" pill class="device-name-hash me-1"
|
||||
value="${this.nameHash}" readonly>
|
||||
<sl-input
|
||||
id="vmDeviceCard${this.objectID}NameHash"
|
||||
size="small"
|
||||
pill
|
||||
class="device-name-hash me-1"
|
||||
value="${this.nameHash.toString()}"
|
||||
readonly
|
||||
>
|
||||
<span slot="prefix">Hash</span>
|
||||
<sl-copy-button slot="suffix" from="vmDeviceCard${this.objectID}NameHash.value"></sl-copy-button>
|
||||
<sl-copy-button
|
||||
slot="suffix"
|
||||
from="vmDeviceCard${this.objectID}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>
|
||||
`;
|
||||
@@ -199,13 +251,14 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
|
||||
"slots",
|
||||
html`
|
||||
<div class="flex flex-row flex-wrap">
|
||||
${repeat(this.slots,
|
||||
(slot, index) => slot.typ + index.toString(),
|
||||
(_slot, index) => html`
|
||||
${repeat(
|
||||
this.slots,
|
||||
(slot, index) => slot.typ + index.toString(),
|
||||
(_slot, index) => html`
|
||||
<vm-device-slot .deviceID=${this.objectID} .slotIndex=${index} class-"flex flex-row max-w-lg mr-2 mb-2">
|
||||
</vm-device-slot>
|
||||
`,
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
`,
|
||||
);
|
||||
@@ -219,15 +272,26 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
|
||||
const vmNetworks = window.VM.vm.networks;
|
||||
const networks = this.connections.map((connection, index, _conns) => {
|
||||
const conn =
|
||||
typeof connection === "object" ? connection.CableNetwork : null;
|
||||
typeof connection === "object" && "CableNetwork" in connection
|
||||
? connection.CableNetwork
|
||||
: null;
|
||||
return html`
|
||||
<sl-select hoist placement="top" clearable key=${index} value=${conn?.net} ?disabled=${conn===null}
|
||||
@sl-change=${this._handleChangeConnection}>
|
||||
<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.toString()}>Network ${net}</sl-option>`,
|
||||
)}
|
||||
(net) =>
|
||||
html`<sl-option value=${net.toString()}
|
||||
>Network ${net}</sl-option
|
||||
>`,
|
||||
)}
|
||||
<span slot="prefix"> ${conn?.typ} </span>
|
||||
</sl-select>
|
||||
`;
|
||||
@@ -243,7 +307,7 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
|
||||
"pins",
|
||||
html`<div class="pins">
|
||||
<vm-device-pins .deviceID=${this.objectID}></vm-device-pins>
|
||||
</div>`
|
||||
</div>`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -295,7 +359,9 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
|
||||
<sl-tab slot="nav" panel="slots">Slots</sl-tab>
|
||||
<sl-tab slot="nav" panel="reagents" disabled>Reagents</sl-tab>
|
||||
<sl-tab slot="nav" panel="networks">Networks</sl-tab>
|
||||
<sl-tab slot="nav" panel="pins" ?disabled=${!this.obj.pins}>Pins</sl-tab>
|
||||
<sl-tab slot="nav" panel="pins" ?disabled=${!this.numPins}
|
||||
>Pins</sl-tab
|
||||
>
|
||||
|
||||
<sl-tab-panel name="fields" active>
|
||||
${until(this.renderFields(), html`<sl-spinner></sl-spinner>`)}
|
||||
@@ -309,21 +375,37 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
|
||||
<sl-tab-panel name="networks">
|
||||
${until(this.renderNetworks(), html`<sl-spinner></sl-spinner>`)}
|
||||
</sl-tab-panel>
|
||||
<sl-tab-panel name="pins">${until(this.renderPins(), html`<sl-spinner></sl-spinner>`)} </sl-tab-panel>
|
||||
<sl-tab-panel name="pins"
|
||||
>${until(this.renderPins(), html`<sl-spinner></sl-spinner>`)}
|
||||
</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.objectID} : ${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>
|
||||
`;
|
||||
@@ -387,5 +469,4 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMObjectMixin(BaseElement)) {
|
||||
);
|
||||
this.updateDevice();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -151,11 +151,11 @@ export class VMDeviceList extends BaseElement {
|
||||
for (const device_id of this.devices) {
|
||||
const device = window.VM.vm.objects.get(device_id);
|
||||
if (device) {
|
||||
if (typeof device.name !== "undefined") {
|
||||
datapoints.push([device.name, device.id]);
|
||||
if (typeof device.obj_info.name !== "undefined") {
|
||||
datapoints.push([device.obj_info.name, device.obj_info.id]);
|
||||
}
|
||||
if (typeof device.prefabName !== "undefined") {
|
||||
datapoints.push([device.prefabName, device.id]);
|
||||
if (typeof device.obj_info.prefab !== "undefined") {
|
||||
datapoints.push([device.obj_info.prefab, device.obj_info.id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { html, css } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { BaseElement, defaultCss } from "components";
|
||||
import { VMDeviceDBMixin, VMObjectMixin } from "virtual_machine/base_device";
|
||||
import { VMTemplateDBMixin, VMObjectMixin } from "virtual_machine/base_device";
|
||||
import { displayNumber, parseNumber } from "utils";
|
||||
import type { LogicType } from "ic10emu_wasm";
|
||||
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
|
||||
|
||||
@customElement("vm-device-fields")
|
||||
export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
export class VMDeviceSlot extends VMObjectMixin(VMTemplateDBMixin(BaseElement)) {
|
||||
constructor() {
|
||||
super();
|
||||
this.subscribe("fields");
|
||||
}
|
||||
|
||||
render() {
|
||||
const fields = Array.from(this.fields.entries());
|
||||
const fields = Array.from(this.logicFields.entries());
|
||||
const inputIdBase = `vmDeviceCard${this.objectID}Field`;
|
||||
return html`
|
||||
${fields.map(([name, field], _index, _fields) => {
|
||||
@@ -34,7 +34,7 @@ export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
const val = parseNumber(input.value);
|
||||
window.VM.get().then((vm) => {
|
||||
if (!vm.setObjectField(this.objectID, field, val, true)) {
|
||||
input.value = this.fields.get(field).value.toString();
|
||||
input.value = this.logicFields.get(field).value.toString();
|
||||
}
|
||||
this.updateDevice();
|
||||
});
|
||||
|
||||
@@ -5,11 +5,11 @@ import "./add_device"
|
||||
import "./slot_add_dialog"
|
||||
import "./slot"
|
||||
|
||||
import { VmDeviceTemplate } from "./template";
|
||||
import { VmObjectTemplate } from "./template";
|
||||
import { VMDeviceCard } from "./card";
|
||||
import { VMDeviceList } from "./device_list";
|
||||
import { VMAddDeviceButton } from "./add_device";
|
||||
import { VMSlotAddDialog } from "./slot_add_dialog";
|
||||
|
||||
export { VMDeviceCard, VmDeviceTemplate, VMDeviceList, VMAddDeviceButton, VMSlotAddDialog };
|
||||
export { VMDeviceCard, VmObjectTemplate as VmDeviceTemplate, VMDeviceList, VMAddDeviceButton, VMSlotAddDialog };
|
||||
|
||||
|
||||
@@ -1,34 +1,42 @@
|
||||
|
||||
import { html, css } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { BaseElement, defaultCss } from "components";
|
||||
import { VMDeviceDBMixin, VMObjectMixin } from "virtual_machine/base_device";
|
||||
import { VMTemplateDBMixin, VMObjectMixin } from "virtual_machine/base_device";
|
||||
import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.component.js";
|
||||
import { ObjectID } from "ic10emu_wasm";
|
||||
|
||||
@customElement("vm-device-pins")
|
||||
export class VMDevicePins extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
export class VMDevicePins extends VMObjectMixin(VMTemplateDBMixin(BaseElement)) {
|
||||
constructor() {
|
||||
super();
|
||||
this.subscribe("ic", "visible-devices");
|
||||
}
|
||||
|
||||
render() {
|
||||
const pins = this.pins;
|
||||
const visibleDevices = window.VM.vm.visibleDevices(this.objectID);
|
||||
const pins = new Array(this.numPins ?? 0)
|
||||
.fill(true)
|
||||
.map((_, index) => this.pins.get(index));
|
||||
const visibleDevices = (this.visibleDevices ?? []).map((id) => window.VM.vm.objects.get(id));
|
||||
const pinsHtml = pins?.map(
|
||||
(pin, index) =>
|
||||
html`
|
||||
<sl-select hoist placement="top" clearable key=${index} value=${pin} @sl-change=${this._handleChangePin}>
|
||||
<span slot="prefix">d${index}</span>
|
||||
${visibleDevices.map(
|
||||
(device, _index) =>
|
||||
html`
|
||||
<sl-option value=${device.id}>
|
||||
Device ${device.id} : ${device.name ?? device.prefabName}
|
||||
</sl-option>
|
||||
`,
|
||||
html` <sl-select
|
||||
hoist
|
||||
placement="top"
|
||||
clearable
|
||||
key=${index}
|
||||
value=${pin}
|
||||
@sl-change=${this._handleChangePin}
|
||||
>
|
||||
<span slot="prefix">d${index}</span>
|
||||
${visibleDevices.map(
|
||||
(device, _index) => html`
|
||||
<sl-option value=${device.obj_info.id.toString()}>
|
||||
Device ${device.obj_info.id} :
|
||||
${device.obj_info.name ?? device.obj_info.prefab}
|
||||
</sl-option>
|
||||
`,
|
||||
)}
|
||||
</sl-select>`,
|
||||
</sl-select>`,
|
||||
);
|
||||
return pinsHtml;
|
||||
}
|
||||
@@ -40,5 +48,4 @@ export class VMDevicePins extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
window.VM.get().then((vm) => vm.setDevicePin(this.objectID, pin, val));
|
||||
this.updateDevice();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { html, css } from "lit";
|
||||
import { customElement, property} from "lit/decorators.js";
|
||||
import { BaseElement, defaultCss } from "components";
|
||||
import { VMDeviceDBMixin, VMObjectMixin } from "virtual_machine/base_device";
|
||||
import { VMTemplateDBMixin, VMObjectMixin } from "virtual_machine/base_device";
|
||||
import {
|
||||
clamp,
|
||||
crc32,
|
||||
displayNumber,
|
||||
parseNumber,
|
||||
} from "utils";
|
||||
import {
|
||||
LogicField,
|
||||
LogicSlotType,
|
||||
SlotType,
|
||||
SlotInfo,
|
||||
Class as SlotType,
|
||||
TemplateDatabase,
|
||||
} from "ic10emu_wasm";
|
||||
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
|
||||
import { VMDeviceCard } from "./card";
|
||||
@@ -21,7 +25,7 @@ export interface SlotModifyEvent {
|
||||
}
|
||||
|
||||
@customElement("vm-device-slot")
|
||||
export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
export class VMDeviceSlot extends VMObjectMixin(VMTemplateDBMixin(BaseElement)) {
|
||||
private _slotIndex: number;
|
||||
|
||||
get slotIndex() {
|
||||
@@ -72,8 +76,7 @@ export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
slotOccupantImg(): string {
|
||||
const slot = this.slots[this.slotIndex];
|
||||
if (typeof slot.occupant !== "undefined") {
|
||||
const hashLookup = (this.templateDB ?? {}).names_by_hash ?? {};
|
||||
const prefabName = hashLookup[slot.occupant.prefab_hash] ?? "UnknownHash";
|
||||
const prefabName = slot.occupant.obj_info.prefab;
|
||||
return `img/stationpedia/${prefabName}.png`;
|
||||
} else {
|
||||
return `img/stationpedia/SlotIcon_${slot.typ}.png`;
|
||||
@@ -83,18 +86,16 @@ export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
slotOccupantPrefabName(): string {
|
||||
const slot = this.slots[this.slotIndex];
|
||||
if (typeof slot.occupant !== "undefined") {
|
||||
const hashLookup = (this.templateDB ?? {}).names_by_hash ?? {};
|
||||
const prefabName = hashLookup[slot.occupant.prefab_hash] ?? "UnknownHash";
|
||||
const prefabName = slot.occupant.obj_info.prefab;
|
||||
return prefabName;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
slotOcccupantTemplate(): { name: string; typ: SlotType } | undefined {
|
||||
if (this.templateDB) {
|
||||
const entry = this.templateDB.db[this.prefabName];
|
||||
return entry?.slots[this.slotIndex];
|
||||
slotOcccupantTemplate(): SlotInfo | undefined {
|
||||
if ("slots" in this.obj.template) {
|
||||
return this.obj.template.slots[this.slotIndex];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
@@ -139,10 +140,11 @@ export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
class="absolute bottom-0 right-0 mr-1 mb-1 text-xs
|
||||
text-neutral-200/90 font-mono bg-neutral-500/40 rounded pl-1 pr-1"
|
||||
>
|
||||
<small
|
||||
>${slot.occupant.quantity}/${slot.occupant
|
||||
.max_quantity}</small
|
||||
>
|
||||
<small>
|
||||
${slot.quantity}/${"item" in slot.occupant.template
|
||||
? slot.occupant.template.item.max_quantity
|
||||
: 1}
|
||||
</small>
|
||||
</div>`,
|
||||
)}
|
||||
<div></div>
|
||||
@@ -170,13 +172,20 @@ export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
? html` <sl-input
|
||||
type="number"
|
||||
size="small"
|
||||
.value=${slot.occupant.quantity.toString()}
|
||||
.value=${slot.quantity.toString()}
|
||||
.min=${1}
|
||||
.max=${slot.occupant.max_quantity}
|
||||
.max=${"item" in slot.occupant.template
|
||||
? slot.occupant.template.item.max_quantity
|
||||
: 1}
|
||||
@sl-change=${this._handleSlotQuantityChange}
|
||||
>
|
||||
<div slot="help-text">
|
||||
<span>Max Quantity: ${slot.occupant.max_quantity}</span>
|
||||
<span>
|
||||
Max Quantity:
|
||||
${"item" in slot.occupant.template
|
||||
? slot.occupant.template.item.max_quantity
|
||||
: 1}
|
||||
</span>
|
||||
</div>
|
||||
</sl-input>`
|
||||
: ""}
|
||||
@@ -218,7 +227,13 @@ export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
_handleSlotQuantityChange(e: Event) {
|
||||
const input = e.currentTarget as SlInput;
|
||||
const slot = this.slots[this.slotIndex];
|
||||
const val = clamp(input.valueAsNumber, 1, slot.occupant.max_quantity);
|
||||
const val = clamp(
|
||||
input.valueAsNumber,
|
||||
1,
|
||||
"item" in slot.occupant.template
|
||||
? slot.occupant.template.item.max_quantity
|
||||
: 1,
|
||||
);
|
||||
if (
|
||||
!window.VM.vm.setObjectSlotField(
|
||||
this.objectID,
|
||||
@@ -228,15 +243,15 @@ export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
true,
|
||||
)
|
||||
) {
|
||||
input.value = this.obj
|
||||
.getSlotField(this.slotIndex, "Quantity")
|
||||
.toString();
|
||||
input.value = this.slots[this.slotIndex].quantity.toString();
|
||||
}
|
||||
}
|
||||
|
||||
renderFields() {
|
||||
const inputIdBase = `vmDeviceSlot${this.objectID}Slot${this.slotIndex}Field`;
|
||||
const _fields = this.obj.getSlotFields(this.slotIndex);
|
||||
const _fields =
|
||||
this.slots[this.slotIndex].logicFields ??
|
||||
new Map<LogicSlotType, LogicField>();
|
||||
const fields = Array.from(_fields.entries());
|
||||
|
||||
return html`
|
||||
@@ -269,14 +284,23 @@ export class VMDeviceSlot extends VMObjectMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
let val = parseNumber(input.value);
|
||||
if (field === "Quantity") {
|
||||
const slot = this.slots[this.slotIndex];
|
||||
val = clamp(input.valueAsNumber, 1, slot.occupant.max_quantity);
|
||||
val = clamp(
|
||||
input.valueAsNumber,
|
||||
1,
|
||||
"item" in slot.occupant.template
|
||||
? slot.occupant.template.item.max_quantity
|
||||
: 1,
|
||||
);
|
||||
}
|
||||
window.VM.get().then((vm) => {
|
||||
if (
|
||||
!vm.setObjectSlotField(this.objectID, this.slotIndex, field, val, true)
|
||||
) {
|
||||
input.value = this.obj
|
||||
.getSlotField(this.slotIndex, field)
|
||||
input.value = (
|
||||
this.slots[this.slotIndex].logicFields ??
|
||||
new Map<LogicSlotType, LogicField>()
|
||||
)
|
||||
.get(field)
|
||||
.toString();
|
||||
}
|
||||
this.updateDevice();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { html, css } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import { BaseElement, defaultCss } from "components";
|
||||
import { VMDeviceDBMixin } from "virtual_machine/base_device";
|
||||
import { VMTemplateDBMixin } from "virtual_machine/base_device";
|
||||
import type { DeviceDB, DeviceDBEntry } from "virtual_machine/device_db";
|
||||
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
|
||||
import SlDialog from "@shoelace-style/shoelace/dist/components/dialog/dialog.component.js";
|
||||
@@ -11,7 +11,7 @@ import uFuzzy from "@leeoniya/ufuzzy";
|
||||
import { LogicField, LogicSlotType, SlotOccupantTemplate } from "ic10emu_wasm";
|
||||
|
||||
@customElement("vm-slot-add-dialog")
|
||||
export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
|
||||
export class VMSlotAddDialog extends VMTemplateDBMixin(BaseElement) {
|
||||
static styles = [
|
||||
...defaultCss,
|
||||
css`
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
import type {
|
||||
Connection,
|
||||
DeviceTemplate,
|
||||
ObjectTemplate,
|
||||
LogicField,
|
||||
LogicType,
|
||||
Slot,
|
||||
SlotTemplate,
|
||||
ConnectionCableNetwork,
|
||||
} from "ic10emu_wasm";
|
||||
import { html, css, HTMLTemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import { BaseElement, defaultCss } from "components";
|
||||
|
||||
import type { DeviceDB, DeviceDBEntry } from "virtual_machine/device_db";
|
||||
import { connectionFromDeviceDBConnection } from "./dbutils";
|
||||
import { displayNumber, parseNumber } from "utils";
|
||||
import { crc32, displayNumber, parseNumber } from "utils";
|
||||
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
|
||||
import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.component.js";
|
||||
import { VMDeviceCard } from "./card";
|
||||
import { VMDeviceDBMixin } from "virtual_machine/base_device";
|
||||
import { VMTemplateDBMixin } from "virtual_machine/base_device";
|
||||
|
||||
@customElement("vm-device-template")
|
||||
export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) {
|
||||
export class VmObjectTemplate extends VMTemplateDBMixin(BaseElement) {
|
||||
|
||||
static styles = [
|
||||
...defaultCss,
|
||||
@@ -56,14 +53,14 @@ export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) {
|
||||
|
||||
@state() fields: { [key in LogicType]?: LogicField };
|
||||
@state() slots: SlotTemplate[];
|
||||
@state() template: DeviceTemplate;
|
||||
@state() template: ObjectTemplate;
|
||||
@state() device_id: number | undefined;
|
||||
@state() device_name: string | undefined;
|
||||
@state() connections: Connection[];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.templateDB = window.VM.vm.db;
|
||||
this.templateDB = window.VM.vm.templateDB;
|
||||
}
|
||||
|
||||
private _prefab_name: string;
|
||||
@@ -78,27 +75,27 @@ export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) {
|
||||
this.setupState();
|
||||
}
|
||||
|
||||
get dbDevice(): DeviceDBEntry {
|
||||
return this.templateDB.db[this.prefab_name];
|
||||
get dbTemplate(): ObjectTemplate {
|
||||
return this.templateDB.get( crc32(this.prefab_name));
|
||||
}
|
||||
|
||||
setupState() {
|
||||
|
||||
this.fields = Object.fromEntries(
|
||||
Object.entries(this.dbDevice?.logic ?? {}).map(([lt, ft]) => {
|
||||
const value = lt === "PrefabHash" ? this.dbDevice.hash : 0.0;
|
||||
Object.entries(this.dbTemplate?.logic ?? {}).map(([lt, ft]) => {
|
||||
const value = lt === "PrefabHash" ? this.dbTemplate.prefab.prefab_hash : 0.0;
|
||||
return [lt, { field_type: ft, value } as LogicField];
|
||||
}),
|
||||
);
|
||||
|
||||
this.slots = (this.dbDevice?.slots ?? []).map(
|
||||
this.slots = (this.dbTemplate?.slots ?? []).map(
|
||||
(slot, _index) =>
|
||||
({
|
||||
typ: slot.typ,
|
||||
}) as SlotTemplate,
|
||||
);
|
||||
|
||||
const connections = Object.entries(this.dbDevice?.conn ?? {}).map(
|
||||
const connections = Object.entries(this.dbTemplate?.conn ?? {}).map(
|
||||
([index, conn]) =>
|
||||
[index, connectionFromDeviceDBConnection(conn)] as const,
|
||||
);
|
||||
@@ -203,7 +200,7 @@ export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) {
|
||||
}
|
||||
|
||||
render() {
|
||||
const device = this.dbDevice;
|
||||
const device = this.dbTemplate;
|
||||
return html`
|
||||
<sl-card class="template-card">
|
||||
<div class="header h-20 w-96" slot="header">
|
||||
|
||||
@@ -116,8 +116,15 @@ class VirtualMachine extends (EventTarget as TypedEventTarget<VirtualMachinEvent
|
||||
async updateObjects() {
|
||||
let updateFlag = false;
|
||||
const removedObjects = [];
|
||||
const objectIds = await this.ic10vm.objects;
|
||||
const frozenObjects = await this.ic10vm.freezeObjects(objectIds);
|
||||
let objectIds;
|
||||
let frozenObjects;
|
||||
try {
|
||||
objectIds = await this.ic10vm.objects;
|
||||
frozenObjects = await this.ic10vm.freezeObjects(objectIds);
|
||||
} catch (e) {
|
||||
this.handleVmError(e);
|
||||
return;
|
||||
}
|
||||
const updatedObjects = [];
|
||||
|
||||
for (const [index, id] of objectIds.entries()) {
|
||||
@@ -270,7 +277,14 @@ class VirtualMachine extends (EventTarget as TypedEventTarget<VirtualMachinEvent
|
||||
if (save) this.app.session.save();
|
||||
}
|
||||
|
||||
updateDevice(id: number, save: boolean = true) {
|
||||
async updateDevice(id: number, save: boolean = true) {
|
||||
let frozen;
|
||||
try {
|
||||
frozen = await this.ic10vm.freezeObject(id);
|
||||
this._objects.set(id, frozen);
|
||||
} catch (e) {
|
||||
this.handleVmError(e);
|
||||
}
|
||||
const device = this._objects.get(id);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-device-modified", { detail: device.obj_info.id }),
|
||||
@@ -528,7 +542,7 @@ class VirtualMachine extends (EventTarget as TypedEventTarget<VirtualMachinEvent
|
||||
}
|
||||
}
|
||||
|
||||
getPrograms() : [number, string][] {
|
||||
getPrograms(): [number, string][] {
|
||||
const programs: [number, string][] = Array.from(
|
||||
this._circuitHolders.entries(),
|
||||
).map(([id, ic]) => [id, ic.obj_info.source_code]);
|
||||
|
||||
@@ -15,14 +15,15 @@ use crate::{
|
||||
};
|
||||
|
||||
use stationeers_data::templates::{
|
||||
ConnectionInfo, ConsumerInfo, DeviceInfo, FabricatorInfo, Instruction, InternalAtmoInfo,
|
||||
ItemCircuitHolderTemplate, ItemConsumerTemplate, ItemInfo, ItemLogicMemoryTemplate,
|
||||
ItemLogicTemplate, ItemSlotsTemplate, ItemSuitCircuitHolderTemplate, ItemSuitLogicTemplate,
|
||||
ItemSuitTemplate, ItemTemplate, LogicInfo, MemoryInfo, ObjectTemplate, PrefabInfo, Recipe,
|
||||
RecipeGasMix, RecipeRange, SlotInfo, StructureCircuitHolderTemplate, StructureInfo,
|
||||
StructureLogicDeviceConsumerMemoryTemplate, StructureLogicDeviceConsumerTemplate,
|
||||
StructureLogicDeviceMemoryTemplate, StructureLogicDeviceTemplate, StructureLogicTemplate,
|
||||
StructureSlotsTemplate, StructureTemplate, SuitInfo, ThermalInfo,
|
||||
ConnectionInfo, ConsumerInfo, DeviceInfo, FabricatorInfo, Instruction, InstructionPart,
|
||||
InstructionPartType, InternalAtmoInfo, ItemCircuitHolderTemplate, ItemConsumerTemplate,
|
||||
ItemInfo, ItemLogicMemoryTemplate, ItemLogicTemplate, ItemSlotsTemplate,
|
||||
ItemSuitCircuitHolderTemplate, ItemSuitLogicTemplate, ItemSuitTemplate, ItemTemplate,
|
||||
LogicInfo, MemoryInfo, ObjectTemplate, PrefabInfo, Recipe, RecipeGasMix, RecipeRange, SlotInfo,
|
||||
StructureCircuitHolderTemplate, StructureInfo, StructureLogicDeviceConsumerMemoryTemplate,
|
||||
StructureLogicDeviceConsumerTemplate, StructureLogicDeviceMemoryTemplate,
|
||||
StructureLogicDeviceTemplate, StructureLogicTemplate, StructureSlotsTemplate,
|
||||
StructureTemplate, SuitInfo, ThermalInfo,
|
||||
};
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
@@ -202,9 +203,9 @@ pub fn generate_database(
|
||||
// remove preceding comma if it exists, leave trailing comma intact if it exists, capture
|
||||
// repeating groups of null fields
|
||||
//
|
||||
// https://regex101.com/r/V2tXIa/1
|
||||
// https://regex101.com/r/WFpjHV/1
|
||||
//
|
||||
let null_matcher = regex::Regex::new(r#"(?:(?:,\n)\s*"\w+":\snull)+(,?)"#).unwrap();
|
||||
let null_matcher = regex::Regex::new(r#"(?:(?:,?\n)\s*"\w+":\snull)+(,?)"#).unwrap();
|
||||
let json = null_matcher.replace_all(&json, "$1");
|
||||
write!(&mut database_file, "{json}")?;
|
||||
database_file.flush()?;
|
||||
@@ -1017,10 +1018,66 @@ impl From<&stationpedia::Structure> for StructureInfo {
|
||||
|
||||
impl From<&stationpedia::Instruction> for Instruction {
|
||||
fn from(value: &stationpedia::Instruction) -> Self {
|
||||
let color_re = regex::Regex::new(r"<color=.*?>|</color>").unwrap();
|
||||
let description_stripped = color_re.replace_all(&value.description, "").to_string();
|
||||
// https://regex101.com/r/GVNgq3/1
|
||||
let valid_range_re =
|
||||
regex::Regex::new(r"VALID ONLY AT ADDRESS(?:ES)? (?<start>\d+) (?:TO (?<end>\d+))?")
|
||||
.unwrap();
|
||||
// https://regex101.com/r/jwbISO/1
|
||||
let part_re =
|
||||
regex::Regex::new(r"[ \|]+(?<start>\d+)-(?<end>\d+)[ \|]+(?<name>[A-Z_]+)[ \|]+(?:(?<type>[A-Z]+_[0-9]+)|(?<unused_len>\d+))")
|
||||
.unwrap();
|
||||
let valid = {
|
||||
if let Some(caps) = valid_range_re.captures(&description_stripped) {
|
||||
(
|
||||
caps.name("start").unwrap().as_str().parse().unwrap(),
|
||||
caps.name("end").map(|cap| cap.as_str().parse().unwrap()),
|
||||
)
|
||||
} else {
|
||||
(0, None)
|
||||
}
|
||||
};
|
||||
let parts = {
|
||||
part_re
|
||||
.captures_iter(&description_stripped)
|
||||
.map(|caps| {
|
||||
let typ = caps
|
||||
.name("type")
|
||||
.map(|cap| match cap.as_str() {
|
||||
"BOOL_8" => InstructionPartType::Bool8,
|
||||
"BYTE_8" => InstructionPartType::Byte8,
|
||||
"INT_32" => InstructionPartType::Int32,
|
||||
"UINT_32" => InstructionPartType::UInt32,
|
||||
"SHORT_16" => InstructionPartType::Short16,
|
||||
"USHORT_16" => InstructionPartType::UShort16,
|
||||
s => InstructionPartType::Unknown(s.to_string()),
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let len = caps
|
||||
.name("unused_len")
|
||||
.and_then(|cap| cap.as_str().parse().ok())
|
||||
.unwrap_or(0);
|
||||
InstructionPartType::Unused(len)
|
||||
});
|
||||
InstructionPart {
|
||||
range: (
|
||||
caps.name("start").unwrap().as_str().parse().unwrap(),
|
||||
caps.name("end").unwrap().as_str().parse().unwrap(),
|
||||
),
|
||||
name: caps.name("name").unwrap().as_str().to_string(),
|
||||
typ,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
Instruction {
|
||||
description: value.description.clone(),
|
||||
description_stripped,
|
||||
typ: value.type_.clone(),
|
||||
value: value.value,
|
||||
valid,
|
||||
parts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user