logicsimulator/ts/wire.ts

318 lines
9.5 KiB
TypeScript

// Wire class for the LogicSimulator
// Copyright (C) 2022 Sascha Nitsch
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
interface WireParam {
color?: string;
scale?: number;
extraclass?: string;
}
enum WireState {
float = 0,
up = 1,
down = 2,
high = 3,
low = 4,
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class Wire {
private paths: string[];
private activeD: string;
public autoid: boolean;
private id: string;
private connections: Array<Array<number | string | null>>;
private activeNodes: TriState[];
private draw: boolean;
private color: string | undefined;
private scale: number;
private extraclass: string;
private dot: boolean;
public joined: Wire | null;
private root: SVGElement | undefined;
private state: WireState;
constructor(id: string | null, param: WireParam, physical: boolean) {
this.paths = [];
this.activeD = '';
if (id === null) {
this.autoid = true;
this.id = makeId(10);
} else {
this.autoid = false;
this.id = id;
}
this.connections = [];
this.activeNodes = [];
this.draw = false;
param = param || {};
this.color = param.color;
this.scale = param.scale || 1;
this.extraclass = param.extraclass || '';
this.dot = physical;
this.joined = null;
this.root = undefined;
this.state = WireState.float;
}
getId(): string {
return this.id;
}
connect(points: Array<TriState | Array<number | string | true> | null>) {
this.activeD = '';
let line = false;
let oldx: number | string | undefined = 0;
let oldy: number | string | undefined = 0;
for (let i = 0; i < points.length; ++i) {
let x;
let y;
if (points[i] instanceof TriState) {
const p = <TriState>points[i];
p.connected(this);
x = p.x();
y = p.y();
this.activeNodes.push(p);
} else if (points[i] instanceof Array) {
const p = <Array<number | string | true>>points[i];
x = <number | string>p[0];
y = <number | string>p[1];
if (p[2] === true) {
this.connections.push([x, y]);
}
} else if (points[i] === null) {
this.paths.push(this.activeD);
this.activeD = '';
line = false;
continue;
}
if (
(typeof oldx === 'number' && typeof x === 'number' && Math.abs(oldx - x) > 0.1) ||
(typeof oldy === 'number' && typeof y === 'number' && Math.abs(oldy - y) > 0.1)
) {
if (line) {
this.draw = true;
}
line = true;
this.activeD += x + ',' + y + ' ';
}
oldx = x;
oldy = y;
}
if (this.draw === false) {
this.activeD = '';
} else {
this.paths.push(this.activeD);
}
}
setup(parent: SVGElement) {
if (this.draw) {
if (!document.getElementById('dot')) {
const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
const marker = document.createElementNS('http://www.w3.org/2000/svg', 'marker');
marker.setAttribute('id', 'dot');
marker.setAttribute('viewBox', '0 0 10 10');
marker.setAttribute('refX', '5');
marker.setAttribute('refY', '5');
marker.setAttribute('markerWidth', '8');
marker.setAttribute('markerHeight', '8');
const mcircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
mcircle.setAttribute('cx', '5');
mcircle.setAttribute('cy', '5');
mcircle.setAttribute('r', '2');
marker.appendChild(mcircle);
defs.appendChild(marker);
if (parent.parentNode !== null) {
parent.parentNode.prepend(defs);
}
}
this.root = document.createElementNS('http://www.w3.org/2000/svg', 'g');
this.root.setAttribute(
'class',
'wire' + (this.color ? ' ' + this.color : '') + (this.extraclass ? ' ' + this.extraclass : '')
);
this.root.setAttribute('id', this.id);
// console.log(this.id ,this.paths);
for (let pcount = 0; pcount < this.paths.length; ++pcount) {
const path = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
path.setAttribute('points', this.paths[pcount]);
// console.log(this.d[pcount]);
if (this.dot) {
path.setAttribute('marker', 'url(#dot)');
path.setAttribute('marker-start', 'url(#dot)');
path.setAttribute('marker-end', 'url(#dot)');
}
this.root.appendChild(path);
}
parent.appendChild(this.root);
for (let i = 0; i < this.connections.length; ++i) {
const c = this.connections[i];
if (c !== null && c[0] && c[1]) {
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
circle.setAttribute('cx', c[0].toString());
circle.setAttribute('cy', c[1].toString());
circle.setAttribute('r', (3 * this.scale).toString());
this.root.appendChild(circle);
}
}
}
return this.draw;
}
update(): boolean {
if (!this.root) {
return false;
}
this.root.classList.remove('high', 'low', 'up', 'down', 'float', 'short');
const oldState = this.state;
this.state = WireState.float;
let source = null;
// collect outputs of active components
for (let i = 0; i < this.activeNodes.length; ++i) {
const state = this.activeNodes[i].get();
if (state === WireState.high || state === WireState.low) {
if ((this.state === WireState.high || this.state === WireState.low) && this.state !== state) {
console.log(
'error, boom on',
this.id,
'try to set:',
state,
'previous',
source && source.getParent().getId(),
source && source.getParam().id,
this.state,
'new:',
this.activeNodes[i].getParent().getId(),
this.activeNodes[i].getParam().id,
this
);
this.root.classList.add('short');
return false;
}
this.state = state;
source = this.activeNodes[i];
} else if (state === WireState.up || state === WireState.down) {
if (this.state !== WireState.high && this.state !== WireState.low) {
this.state = state;
source = this.activeNodes[i];
}
}
}
/*if (this.state === "up") {
this.state = true;
} else if (this.state === "down") {
this.state = false;
}*/
// update inputs of active components with new state
for (let i = 0; i < this.activeNodes.length; ++i) {
this.activeNodes[i].setState(this.state);
}
//this.root.classList.add((this.state === true || this.state === "up")? "high" : ((this.state === false || this.state === "down") ? "low" : "float"));
let classname = '';
switch (this.state) {
case WireState.high:
classname = 'high';
break;
case WireState.low:
classname = 'low';
break;
case WireState.up:
classname = 'up';
break;
case WireState.down:
classname = 'down';
break;
default:
classname = 'float';
}
this.root.classList.add(classname);
return oldState !== this.state;
}
join(other: Wire | null) {
if (!other) return;
while (other.joined) {
other = other.joined;
}
if (this.joined) {
this.joined.join(other);
} else {
for (let i = 0; i < other.activeNodes.length; ++i) {
this.activeNodes.push(other.activeNodes[i]);
}
for (let i = 0; i < other.connections.length; ++i) {
this.connections.push(other.connections[i]);
}
other.activeNodes = [];
this.paths = this.paths.concat(other.paths);
other.activeD = '';
other.joined = this;
this.draw ||= other.draw;
other.draw = false;
if (other.color && !this.color) {
this.color = other.color;
}
}
// console.log('join', this.id, this.autoid, 'with', oOther.id, oOther.autoid);
if (this.autoid && !other.autoid) {
const tmp = this.id;
this.id = other.id;
other.id = tmp;
this.autoid = false;
other.autoid = true;
this.color = other.color;
this.scale = other.scale;
}
}
remove(parent: TriState | BaseComponent) {
if (this.joined) {
this.joined.remove(parent);
return;
}
for (let i = 0; i < this.activeNodes.length; ++i) {
if (this.activeNodes[i].getParent() === parent) {
this.activeNodes.splice(i, 1);
i = 0;
}
}
//console.log("rem", this.id, this.activeNodes);
}
getState(): WireState {
return this.state;
}
static WireStateFromString(input: string) {
let s = WireState.float;
switch (input) {
case 'up':
s = WireState.up;
break;
case 'down':
s = WireState.down;
break;
case 'high':
s = WireState.high;
break;
case 'low':
s = WireState.low;
break;
}
return s;
}
}