515 lines
15 KiB
JavaScript
Executable file
515 lines
15 KiB
JavaScript
Executable file
/* eslint-disable import/no-cycle */
|
|
/* eslint-disable no-use-before-define */
|
|
/* eslint-disable no-continue */
|
|
/* eslint-disable no-param-reassign */
|
|
/* eslint-disable no-bitwise */
|
|
import { layoutModeGet, layoutUpdate } from './layoutMode';
|
|
import plotArea from './plotArea';
|
|
import simulationArea from './simulationArea';
|
|
import {
|
|
dots, canvasMessage, findDimensions, rect2,
|
|
} from './canvasApi';
|
|
import { showProperties, prevPropertyObjGet } from './ux';
|
|
import { showError } from './utils';
|
|
import miniMapArea from './minimap';
|
|
import { resetup } from './setup';
|
|
import { verilogModeGet } from './Verilog2CV';
|
|
|
|
/**
|
|
* Core of the simulation and rendering algorithm.
|
|
*/
|
|
|
|
/**
|
|
* @type {number} engine
|
|
* @category engine
|
|
*/
|
|
var wireToBeChecked = 0;
|
|
|
|
/**
|
|
* Used to set wireChecked boolean which updates wires in UI if true (or 1). 2 if some problem and it is handled.
|
|
* @param {number} param - value of wirechecked
|
|
* @category engine
|
|
*/
|
|
export function wireToBeCheckedSet(param) {
|
|
wireToBeChecked = param;
|
|
}
|
|
|
|
/**
|
|
* scheduleUpdate() will be called if true
|
|
* @type {boolean}
|
|
* @category engine
|
|
*/
|
|
var willBeUpdated = false;
|
|
|
|
/**
|
|
* used to set willBeUpdated variable
|
|
* @type {boolean}
|
|
* @category engine
|
|
* @category engine
|
|
*/
|
|
export function willBeUpdatedSet(param) {
|
|
willBeUpdated = param;
|
|
}
|
|
|
|
/**
|
|
* true if we have an element selected and
|
|
* is used when we are paning the grid.
|
|
* @type {boolean}
|
|
* @category engine
|
|
*/
|
|
var objectSelection = false;
|
|
|
|
/**
|
|
* used to set the value of object selection,
|
|
* @param {boolean} param
|
|
* @category engine
|
|
*/
|
|
export function objectSelectionSet(param) {
|
|
objectSelection = param;
|
|
}
|
|
|
|
/**
|
|
* Flag for updating position
|
|
* @type {boolean}
|
|
* @category engine
|
|
*/
|
|
var updatePosition = true;
|
|
|
|
/**
|
|
* used to set the value of updatePosition.
|
|
* @param {boolean} param
|
|
* @category engine
|
|
*/
|
|
export function updatePositionSet(param) {
|
|
updatePosition = param;
|
|
}
|
|
|
|
/**
|
|
* Flag for updating simulation
|
|
* @type {boolean}
|
|
* @category engine
|
|
*/
|
|
var updateSimulation = true;
|
|
|
|
/**
|
|
* used to set the value of updateSimulation.
|
|
* @param {boolean} param
|
|
* @category engine
|
|
*/
|
|
export function updateSimulationSet(param) {
|
|
updateSimulation = param;
|
|
}
|
|
/**
|
|
* Flag for rendering
|
|
* @type {boolean}
|
|
* @category engine
|
|
*/
|
|
var updateCanvas = true;
|
|
|
|
/**
|
|
* used to set the value of updateCanvas.
|
|
* @param {boolean} param
|
|
* @category engine
|
|
*/
|
|
export function updateCanvasSet(param) {
|
|
updateCanvas = param;
|
|
}
|
|
|
|
/**
|
|
* Flag for updating grid
|
|
* @type {boolean}
|
|
* @category engine
|
|
*/
|
|
var gridUpdate = true;
|
|
|
|
/**
|
|
* used to set gridUpdate
|
|
* @param {boolean} param
|
|
* @category engine
|
|
*/
|
|
export function gridUpdateSet(param) {
|
|
gridUpdate = param;
|
|
}
|
|
|
|
/**
|
|
* used to get gridUpdate
|
|
* @return {boolean}
|
|
* @category engine
|
|
*/
|
|
export function gridUpdateGet() {
|
|
return gridUpdate;
|
|
}
|
|
/**
|
|
* Flag for updating grid
|
|
* @type {boolean}
|
|
* @category engine
|
|
*/
|
|
var forceResetNodes = true;
|
|
|
|
/**
|
|
* used to set forceResetNodes
|
|
* @param {boolean} param
|
|
* @category engine
|
|
*/
|
|
export function forceResetNodesSet(param) {
|
|
forceResetNodes = param;
|
|
}
|
|
/**
|
|
* Flag for updating grid
|
|
* @type {boolean}
|
|
* @category engine
|
|
*/
|
|
var errorDetected = false;
|
|
|
|
/**
|
|
* used to set errorDetected
|
|
* @param {boolean} param
|
|
* @category engine
|
|
*/
|
|
export function errorDetectedSet(param) {
|
|
errorDetected = param;
|
|
}
|
|
|
|
/**
|
|
* used to set errorDetected
|
|
* @returns {boolean} errorDetected
|
|
* @category engine
|
|
*/
|
|
export function errorDetectedGet() {
|
|
return errorDetected;
|
|
}
|
|
|
|
/**
|
|
* details of where and what canvas message has to be shown.
|
|
* @type {Object}
|
|
* @property {number} x - x cordinate of message
|
|
* @property {number} y - x cordinate of message
|
|
* @property {number} string - the message
|
|
* @category engine
|
|
*/
|
|
export var canvasMessageData = {
|
|
x: undefined,
|
|
y: undefined,
|
|
string: undefined,
|
|
};
|
|
|
|
/**
|
|
* Flag for updating subCircuits
|
|
* @type {boolean}
|
|
* @category engine
|
|
*/
|
|
var updateSubcircuit = true;
|
|
|
|
/**
|
|
* used to set updateSubcircuit
|
|
* @param {boolean} param
|
|
* @category engine
|
|
*/
|
|
export function updateSubcircuitSet(param) {
|
|
if (updateSubcircuit != param) {
|
|
updateSubcircuit = param;
|
|
return true;
|
|
}
|
|
updateSubcircuit = param;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* turn light mode on
|
|
* @param {boolean} val -- new value for light mode
|
|
* @category engine
|
|
*/
|
|
export function changeLightMode(val) {
|
|
if (!val && lightMode) {
|
|
lightMode = false;
|
|
DPR = window.devicePixelRatio || 1;
|
|
globalScope.scale *= DPR;
|
|
} else if (val && !lightMode) {
|
|
lightMode = true;
|
|
globalScope.scale /= DPR;
|
|
DPR = 1
|
|
$('#miniMap').fadeOut('fast');
|
|
}
|
|
resetup();
|
|
}
|
|
|
|
/**
|
|
* Function to render Canvas according th renderupdate order
|
|
* @param {Scope} scope - The circuit whose canvas we want to render
|
|
* @category engine
|
|
*/
|
|
export function renderCanvas(scope) {
|
|
if (layoutModeGet() || verilogModeGet()) { // Different Algorithm
|
|
return;
|
|
}
|
|
var ctx = simulationArea.context;
|
|
// Reset canvas
|
|
simulationArea.clear();
|
|
// Update Grid
|
|
if (gridUpdate) {
|
|
gridUpdateSet(false);
|
|
dots();
|
|
}
|
|
canvasMessageData = {
|
|
x: undefined,
|
|
y: undefined,
|
|
string: undefined,
|
|
}; // Globally set in draw fn ()
|
|
// Render objects
|
|
for (let i = 0; i < renderOrder.length; i++) {
|
|
for (var j = 0; j < scope[renderOrder[i]].length; j++) { scope[renderOrder[i]][j].draw(); }
|
|
}
|
|
// Show any message
|
|
if (canvasMessageData.string !== undefined) {
|
|
canvasMessage(ctx, canvasMessageData.string, canvasMessageData.x, canvasMessageData.y);
|
|
}
|
|
// If multiple object selections are going on, show selected area
|
|
if (objectSelection) {
|
|
ctx.beginPath();
|
|
ctx.lineWidth = 2;
|
|
ctx.strokeStyle = 'black';
|
|
ctx.fillStyle = 'rgba(0,0,0,0.1)';
|
|
rect2(ctx, simulationArea.mouseDownX, simulationArea.mouseDownY, simulationArea.mouseX - simulationArea.mouseDownX, simulationArea.mouseY - simulationArea.mouseDownY, 0, 0, 'RIGHT');
|
|
ctx.stroke();
|
|
ctx.fill();
|
|
}
|
|
if (simulationArea.hover !== undefined) {
|
|
simulationArea.canvas.style.cursor = 'pointer';
|
|
} else if (simulationArea.mouseDown) {
|
|
simulationArea.canvas.style.cursor = 'grabbing';
|
|
} else {
|
|
simulationArea.canvas.style.cursor = 'default';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function to move multiple objects and panes window
|
|
* deselected using dblclick right now (PR open for esc key)
|
|
* @param {Scope=} scope - the circuit in which we are selecting stuff
|
|
* @category engine
|
|
*/
|
|
export function updateSelectionsAndPane(scope = globalScope) {
|
|
if (!simulationArea.selected && simulationArea.mouseDown) {
|
|
simulationArea.selected = true;
|
|
simulationArea.lastSelected = scope.root;
|
|
simulationArea.hover = scope.root;
|
|
// Selecting multiple objects
|
|
if (simulationArea.shiftDown) {
|
|
objectSelectionSet(true);
|
|
} else if (!embed) {
|
|
findDimensions(scope);
|
|
miniMapArea.setup();
|
|
$('#miniMap').show();
|
|
}
|
|
} else if (simulationArea.lastSelected === scope.root && simulationArea.mouseDown) {
|
|
// pane canvas to give an idea of grid moving
|
|
if (!objectSelection) {
|
|
globalScope.ox = (simulationArea.mouseRawX - simulationArea.mouseDownRawX) + simulationArea.oldx;
|
|
globalScope.oy = (simulationArea.mouseRawY - simulationArea.mouseDownRawY) + simulationArea.oldy;
|
|
globalScope.ox = Math.round(globalScope.ox);
|
|
globalScope.oy = Math.round(globalScope.oy);
|
|
gridUpdateSet(true);
|
|
if (!embed && !lightMode) miniMapArea.setup();
|
|
} else {
|
|
// idea: kind of empty
|
|
}
|
|
} else if (simulationArea.lastSelected === scope.root) {
|
|
/*
|
|
Select multiple objects by adding them to the array
|
|
simulationArea.multipleObjectSelections when we select
|
|
using shift + mouse movement to select an area but
|
|
not shift + click
|
|
*/
|
|
simulationArea.lastSelected = undefined;
|
|
simulationArea.selected = false;
|
|
simulationArea.hover = undefined;
|
|
if (objectSelection) {
|
|
objectSelectionSet(false);
|
|
var x1 = simulationArea.mouseDownX;
|
|
var x2 = simulationArea.mouseX;
|
|
var y1 = simulationArea.mouseDownY;
|
|
var y2 = simulationArea.mouseY;
|
|
// Sort those four points to make a selection pane
|
|
if (x1 > x2) {
|
|
const temp = x1;
|
|
x1 = x2;
|
|
x2 = temp;
|
|
}
|
|
if (y1 > y2) {
|
|
const temp = y1;
|
|
y1 = y2;
|
|
y2 = temp;
|
|
}
|
|
// Select the objects, push them into a list
|
|
for (let i = 0; i < updateOrder.length; i++) {
|
|
for (var j = 0; j < scope[updateOrder[i]].length; j++) {
|
|
var obj = scope[updateOrder[i]][j];
|
|
if (simulationArea.multipleObjectSelections.contains(obj)) continue;
|
|
var x; var
|
|
y;
|
|
if (obj.objectType === 'Node') {
|
|
x = obj.absX();
|
|
y = obj.absY();
|
|
} else if (obj.objectType !== 'Wire') {
|
|
x = obj.x;
|
|
y = obj.y;
|
|
} else {
|
|
continue;
|
|
}
|
|
if (x > x1 && x < x2 && y > y1 && y < y2) {
|
|
simulationArea.multipleObjectSelections.push(obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main fn that resolves circuit using event driven simulation
|
|
* All inputs are added to a scope using scope.addinput() and
|
|
* the simulation starts to play.
|
|
* @param {Scope=} scope - the circuit we want to simulate
|
|
* @param {boolean} resetNodes - boolean to reset all nodes
|
|
* @category engine
|
|
*/
|
|
export function play(scope = globalScope, resetNodes = false) {
|
|
if (errorDetected) return; // Don't simulate until error is fixed
|
|
if (loading === true) return; // Don't simulate until loaded
|
|
|
|
simulationArea.simulationQueue.reset();
|
|
plotArea.setExecutionTime(); // Waveform thing
|
|
// Reset Nodes if required
|
|
if (resetNodes || forceResetNodes) {
|
|
scope.reset();
|
|
simulationArea.simulationQueue.reset();
|
|
forceResetNodesSet(false);
|
|
}
|
|
|
|
// To store list of circuitselements that have shown contention but kept temporarily
|
|
// Mainly to resolve tristate bus issues
|
|
simulationArea.contentionPending = [];
|
|
// add inputs to the simulation queue
|
|
scope.addInputs();
|
|
// to check if we have infinite loop in circuit
|
|
let stepCount = 0;
|
|
let elem;
|
|
while (!simulationArea.simulationQueue.isEmpty()) {
|
|
if (errorDetected) {
|
|
simulationArea.simulationQueue.reset();
|
|
return;
|
|
}
|
|
elem = simulationArea.simulationQueue.pop();
|
|
elem.resolve();
|
|
stepCount++;
|
|
if (stepCount > 1000000) { // Cyclic or infinite Circuit Detection
|
|
showError('Simulation Stack limit exceeded: maybe due to cyclic paths or contention');
|
|
errorDetectedSet(true);
|
|
forceResetNodesSet(true);
|
|
}
|
|
}
|
|
// Check for TriState Contentions
|
|
if (simulationArea.contentionPending.length) {
|
|
showError('Contention at TriState');
|
|
forceResetNodesSet(true);
|
|
errorDetectedSet(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function to check for any UI update, it is throttled by time
|
|
* @param {number=} count - this is used to force update
|
|
* @param {number=} time - the time throttling parameter
|
|
* @param {function} fn - function to run before updating UI
|
|
* @category engine
|
|
*/
|
|
export function scheduleUpdate(count = 0, time = 100, fn) {
|
|
if (lightMode) time *= 5;
|
|
var updateFn = layoutModeGet() ? layoutUpdate : update;
|
|
if (count) { // Force update
|
|
updateFn();
|
|
for (let i = 0; i < count; i++) { setTimeout(updateFn, 10 + 50 * i); }
|
|
}
|
|
if (willBeUpdated) return; // Throttling
|
|
willBeUpdatedSet(true);
|
|
// Call a function before update ..
|
|
if (fn) {
|
|
setTimeout(() => {
|
|
fn();
|
|
updateFn();
|
|
}, time);
|
|
} else setTimeout(updateFn, time);
|
|
}
|
|
|
|
/**
|
|
* fn that calls update on everything else. If any change
|
|
* is there, it resolves the circuit and draws it again.
|
|
* Also updates simulations, selection, minimap, resolves
|
|
* circuit and redraws canvas if required.
|
|
* @param {Scope=} scope - the circuit to be updated
|
|
* @param {boolean=} updateEverything - if true we update the wires, nodes and modules
|
|
* @category engine
|
|
*/
|
|
export function update(scope = globalScope, updateEverything = false) {
|
|
willBeUpdatedSet(false);
|
|
if (loading === true || layoutModeGet()) return;
|
|
var updated = false;
|
|
simulationArea.hover = undefined;
|
|
// Update wires
|
|
if (wireToBeChecked || updateEverything) {
|
|
if (wireToBeChecked === 2) wireToBeChecked = 0; // this required due to timing issues
|
|
else wireToBeChecked++;
|
|
// WHY IS THIS REQUIRED ???? we are checking inside wire ALSO
|
|
// Idea: we can just call length again instead of doing it during loop.
|
|
var prevLength = scope.wires.length;
|
|
for (let i = 0; i < scope.wires.length; i++) {
|
|
scope.wires[i].checkConnections();
|
|
if (scope.wires.length !== prevLength) {
|
|
prevLength--;
|
|
i--;
|
|
}
|
|
}
|
|
scheduleUpdate();
|
|
}
|
|
// Update subcircuits
|
|
if (updateSubcircuit || updateEverything) {
|
|
for (let i = 0; i < scope.SubCircuit.length; i++) { scope.SubCircuit[i].reset(); }
|
|
updateSubcircuitSet(false);
|
|
}
|
|
// Update UI position
|
|
if (updatePosition || updateEverything) {
|
|
for (let i = 0; i < updateOrder.length; i++) {
|
|
for (let j = 0; j < scope[updateOrder[i]].length; j++) {
|
|
updated |= scope[updateOrder[i]][j].update();
|
|
}
|
|
}
|
|
}
|
|
// Updates multiple objectselections and panes window
|
|
if (updatePosition || updateEverything) {
|
|
updateSelectionsAndPane(scope);
|
|
}
|
|
// Update MiniMap
|
|
if (!embed && simulationArea.mouseDown && simulationArea.lastSelected && simulationArea.lastSelected !== globalScope.root) {
|
|
if (!lightMode) { $('#miniMap').fadeOut('fast'); }
|
|
}
|
|
// Run simulation
|
|
if (updateSimulation) {
|
|
play();
|
|
}
|
|
// Show properties of selected element
|
|
if (!embed && prevPropertyObjGet() !== simulationArea.lastSelected) {
|
|
if (simulationArea.lastSelected && simulationArea.lastSelected.objectType !== 'Wire') {
|
|
// ideas: why show properties of project in Nodes but not wires?
|
|
showProperties(simulationArea.lastSelected);
|
|
} else {
|
|
// hideProperties();
|
|
}
|
|
}
|
|
// Draw, render everything
|
|
if (updateCanvas) {
|
|
renderCanvas(scope);
|
|
}
|
|
updateSimulationSet(false);
|
|
updateCanvas = false;
|
|
updatePositionSet(false);
|
|
}
|