318 lines
9.5 KiB
TypeScript
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;
|
|
}
|
|
}
|