CircuitOnline/public/js/logix.js

1536 lines
50 KiB
JavaScript

// Size of canvas
var width;
var height;
var listenToSimulator=true; //enables key down listener on the simulator
var createNode=false //Flag to create node when its value ==true
var stopWire=true //flag for stopoing making Nodes when the second terminal reaches a Node (closed path)
uniqueIdCounter = 0; // To be deprecated
unit = 10; // size of each division/ not used everywhere, to be deprecated
wireToBeChecked = 0; // when node disconnects from another node
willBeUpdated = false; // scheduleUpdate() will be called if true
objectSelection = false; // Flag for object selection
errorDetected = false; // Flag for error detection
prevErrorMessage = undefined; // Global variable for error messages
prevShowMessage = undefined; // Global variable for error messages
updatePosition = true; // Flag for updating position
updateSimulation = true; // Flag for updating simulation
updateCanvas = true; // Flag for rendering
gridUpdate = true; // Flag for updating grid
updateSubcircuit = true; // Flag for updating subCircuits
loading = false; // Flag - all assets are loaded
DPR = 1; // devicePixelRatio, 2 for retina displays, 1 for low resolution displays
projectSaved = true; // Flag for project saved or not
lightMode = false; // To be deprecated
layoutMode = false; // Flag for mode
forceResetNodes = true; // FLag to reset all Nodes
//Exact same name as object constructor
//This list needs to be updated when new circuitselements are created
function setupElementLists() {
$('#menu').empty();
window.circuitElementList = metadata.circuitElementList;
window.annotationList = metadata.annotationList;
window.inputList = metadata.inputList;
window.subCircuitInputList = metadata.subCircuitInputList;
window.moduleList = [...circuitElementList, ...annotationList]
window.updateOrder = ["wires", ...circuitElementList, "nodes", ...annotationList]; // Order of update
window.renderOrder = [...(moduleList.slice().reverse()), "wires", "allNodes"]; // Order of render
function createIcon(element) {
return `<div class="icon logixModules pointerCursor" id="${element}" >
<img src= "/img/${element}.svg" >
<p class="img__description">${element}</p>
</div>`;
}
let elementHierarchy = metadata.elementHierarchy;
for (category in elementHierarchy) {
let htmlIcons = '';
let categoryData = elementHierarchy[category];
for (let i = 0; i < categoryData.length; i++) {
let element = categoryData[i];
htmlIcons += createIcon(element);
}
let accordionData = `<div class="panelHeader">${category}</div>
<div class="panel" style="overflow-y:hidden;">
${htmlIcons}
</div>`;
$('#menu').append(accordionData);
}
}
// setupElementLists()
// circuitElementList = [
// "Input", "Output", "NotGate", "OrGate", "AndGate", "NorGate", "NandGate", "XorGate", "XnorGate", "SevenSegDisplay", "SixteenSegDisplay", "HexDisplay",
// "Multiplexer", "BitSelector", "Splitter", "Power", "Ground", "ConstantVal", "ControlledInverter", "TriState", "Adder", "Rom", "RAM", "EEPROM", "TflipFlop",
// "JKflipFlop", "SRflipFlop", "DflipFlop", "TTY", "Keyboard", "Clock", "DigitalLed", "Stepper", "VariableLed", "RGBLed", "SquareRGBLed", "RGBLedMatrix", "Button", "Demultiplexer",
// "Buffer", "SubCircuit", "Flag", "MSB", "LSB", "PriorityEncoder", "Tunnel", "ALU", "Decoder", "Random", "Counter", "Dlatch", "TB_Input", "TB_Output", "ForceGate",
// ];
// annotationList = ["Text", "Rectangle", "Arrow"]
// moduleList = [...circuitElementList, ...annotationList]
// updateOrder = ["wires", ...circuitElementList, "nodes", ...annotationList]; // Order of update
// renderOrder = [...(moduleList.slice().reverse()), "wires", "allNodes"]; // Order of render
// // Exact same name as object constructor
// // All the combinational modules which give rise to an value(independently)
// inputList = ["Random","Buffer", "Stepper", "Ground", "Power", "ConstantVal", "Input", "Clock", "Button","Dlatch","JKflipFlop","TflipFlop","SRflipFlop","DflipFlop"];
// subCircuitInputList=["Clock", "Button","Buffer", "Stepper", "Ground", "Power", "ConstantVal","Dlatch","JKflipFlop","TflipFlop","SRflipFlop","DflipFlop"]
// inputList = ["Random", "Dlatch", "JKflipFlop", "TflipFlop", "SRflipFlop", "DflipFlop", "Buffer", "Stepper", "Ground", "Power", "ConstantVal", "Input", "Clock", "Button", "Counter"];
// subCircuitInputList = ["Random", "Dlatch", "JKflipFlop", "TflipFlop", "SRflipFlop", "DflipFlop", "Buffer", "Stepper", "Ground", "Power", "ConstantVal", "Clock", "Button", "Counter"];
//Scope object for each circuit level, globalScope for outer level
scopeList = {};
// Helper function to show error
function showError(error) {
errorDetected = true;
if (error == prevErrorMessage) return;
prevErrorMessage = error;
var id = Math.floor(Math.random() * 10000);
$('#MessageDiv').append("<div class='alert alert-danger' role='alert' id='" + id + "'> " + error + "</div>");
setTimeout(function() {
prevErrorMessage = undefined;
$('#' + id).fadeOut();
}, 1500);
}
function showRestricted() {
$('#restrictedDiv').removeClass("display--none");
// Show no help text for restricted elements
$("#Help").removeClass("show");
$('#restrictedDiv').html("The element has been restricted by mentor. Usage might lead to deduction in marks");
}
function hideRestricted() {
$('#restrictedDiv').addClass("display--none");
}
function updateRestrictedElementsList() {
if (restrictedElements.length === 0) return;
const restrictedCircuitElementsUsed = globalScope.restrictedCircuitElementsUsed;
let restrictedStr = "";
restrictedCircuitElementsUsed.forEach((element) => {
restrictedStr += `${element}, `;
});
if (restrictedStr === "") {
restrictedStr = "None";
} else {
restrictedStr = restrictedStr.slice(0, -2);
}
$("#restrictedElementsDiv--list").html(restrictedStr);
}
function updateRestrictedElementsInScope(scope = globalScope) {
// Do nothing if no restricted elements
if (restrictedElements.length === 0) return;
let restrictedElementsUsed = [];
restrictedElements.forEach((element) => {
if (scope[element].length > 0) {
restrictedElementsUsed.push(element);
}
});
scope.restrictedCircuitElementsUsed = restrictedElementsUsed;
updateRestrictedElementsList();
}
// Helper function to show message
function showMessage(mes) {
if (mes == prevShowMessage) return;
prevShowMessage = mes
var id = Math.floor(Math.random() * 10000);
$('#MessageDiv').append("<div class='alert alert-success' role='alert' id='" + id + "'> " + mes + "</div>");
setTimeout(function() {
prevShowMessage = undefined;
$('#' + id).fadeOut()
}, 2500);
}
// Helper function to open a new tab
function openInNewTab(url) {
var win = window.open(url, '_blank');
win.focus();
}
// Following function need to be improved - remove mutability etc
//fn to remove elem in array
Array.prototype.clean = function(deleteValue) {
for (var i = 0; i < this.length; i++) {
if (this[i] == deleteValue) {
this.splice(i, 1);
i--;
}
}
return this;
};
// Following function need to be improved
Array.prototype.extend = function(other_array) {
/* you should include a test to check whether other_array really is an array */
other_array.forEach(function(v) {
this.push(v)
}, this);
}
// Following function need to be improved
//fn to check if an elem is in an array
Array.prototype.contains = function(value) {
return this.indexOf(value) > -1
};
// Helper function to return unique list
function uniq(a) {
var seen = {};
return a.filter(function(item) {
return seen.hasOwnProperty(item) ? false : (seen[item] = true);
});
}
// Currently Focussed circuit/scope
globalScope = undefined;
// Base circuit class
// All circuits are stored in a scope
function Scope(name = "localScope", id = undefined) {
this.restrictedCircuitElementsUsed = [];
this.id = id || Math.floor((Math.random() * 100000000000) + 1);
this.CircuitElement = [];
//root object for referring to main canvas - intermediate node uses this
this.root = new CircuitElement(0, 0, this, "RIGHT", 1);
this.backups = [];
this.timeStamp = new Date().getTime();
this.ox = 0;
this.oy = 0;
this.scale = DPR;
this.tunnelList = {};
this.stack = []
this.name = name;
this.pending = []
this.nodes = []; //intermediate nodes only
this.allNodes = [];
this.wires = [];
// Creating arrays for other module elements
for (var i = 0; i < moduleList.length; i++) {
this[moduleList[i]] = [];
}
// Setting default layout
this.layout = { // default position
width: 100,
height: 40,
title_x: 50,
title_y: 13,
titleEnabled: true,
}
// FOR SOME UNKNOWN REASON, MAKING THE COPY OF THE LIST COMMON
// TO ALL SCOPES EITHER BY PROTOTYPE OR JUST BY REFERNCE IS CAUSING ISSUES
// The issue comes regarding copy/paste operation, after 5-6 operations it becomes slow for unknown reasons
// CHANGE/ REMOVE WITH CAUTION
// this.objects = ["wires", ...circuitElementList, "nodes", ...annotationList];
// this.renderObjectOrder = [ ...(moduleList.slice().reverse()), "wires", "allNodes"];
}
// Resets all nodes recursively
Scope.prototype.reset = function() {
for (var i = 0; i < this.allNodes.length; i++)
this.allNodes[i].reset();
for (var i = 0; i < this.Splitter.length; i++) {
this.Splitter[i].reset();
}
for (var i = 0; i < this.SubCircuit.length; i++) {
this.SubCircuit[i].reset();
}
}
// Adds all inputs to simulationQueue
Scope.prototype.addInputs = function() {
for (var i = 0; i < inputList.length; i++) {
for (var j = 0; j < this[inputList[i]].length; j++) {
simulationArea.simulationQueue.add(this[inputList[i]][j], 0);
}
}
for (let j = 0; j < this.SubCircuit.length; j++)
this.SubCircuit[j].addInputs();
}
// Ticks clocks recursively -- needs to be deprecated and synchronize all clocks with a global clock
Scope.prototype.clockTick = function() {
for (var i = 0; i < this.Clock.length; i++)
this.Clock[i].toggleState(); //tick clock!
for (var i = 0; i < this.SubCircuit.length; i++)
this.SubCircuit[i].localScope.clockTick(); //tick clock!
}
// Checks if this circuit contains directly or indirectly scope with id
// Recursive nature
Scope.prototype.checkDependency = function(id) {
if (id == this.id) return true;
for (var i = 0; i < this.SubCircuit.length; i++)
if (this.SubCircuit[i].id == id) return true;
for (var i = 0; i < this.SubCircuit.length; i++)
if (scopeList[this.SubCircuit[i].id].checkDependency(id)) return true;
return false
}
// Get dependency list - list of all circuits, this circuit depends on
Scope.prototype.getDependencies = function() {
var list = []
for (var i = 0; i < this.SubCircuit.length; i++) {
list.push(this.SubCircuit[i].id);
list.extend(scopeList[this.SubCircuit[i].id].getDependencies());
}
return uniq(list);
}
// helper function to reduce layout size
Scope.prototype.fixLayout = function() {
var max_y = 20;
for (var i = 0; i < this.Input.length; i++)
max_y = Math.max(this.Input[i].layoutProperties.y, max_y)
for (var i = 0; i < this.Output.length; i++)
max_y = Math.max(this.Output[i].layoutProperties.y, max_y)
if (max_y != this.layout.height)
this.layout.height = max_y + 10;
}
// Function which centers the circuit to the correct zoom level
Scope.prototype.centerFocus = function(zoomIn = true) {
if (layoutMode) return;
findDimensions(this);
var minX = simulationArea.minWidth || 0;
var minY = simulationArea.minHeight || 0;
var maxX = simulationArea.maxWidth || 0;
var maxY = simulationArea.maxHeight || 0;
var reqWidth = maxX - minX + 150;
var reqHeight = maxY - minY + 150;
this.scale = Math.min(width / reqWidth, height / reqHeight)
if (!zoomIn)
this.scale = Math.min(this.scale, DPR);
this.scale = Math.max(this.scale, DPR / 10);
this.ox = (-minX) * this.scale + (width - (maxX - minX) * this.scale) / 2;
this.oy = (-minY) * this.scale + (height - (maxY - minY) * this.scale) / 2;
}
//fn to setup environment
function setupEnvironment() {
projectId = generateId();
updateSimulation = true;
DPR = window.devicePixelRatio || 1;
newCircuit("Main");
data = {}
resetup();
}
function setup() {
setupElementLists();
setupEnvironment();
if (!embed)
setupUI();
startListeners();
// Load project data after 1 second - needs to be improved, delay needs to be eliminated
setTimeout(function() {
if (logix_project_id != 0) {
$('.loadingIcon').fadeIn();
$.ajax({
url: '/simulator/get_data',
type: 'POST',
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))
},
data: {
"id": logix_project_id
},
success: function(response) {
data = (response);
if (data) {
load(data);
simulationArea.changeClockTime(data["timePeriod"] || 500);
}
$('.loadingIcon').fadeOut();
},
failure: function() {
alert("Error: could not load ");
$('.loadingIcon').fadeOut();
}
});
}
// Restore unsaved data and save
else if (localStorage.getItem("recover_login") && userSignedIn) {
var data = JSON.parse(localStorage.getItem("recover_login"));
load(data);
localStorage.removeItem("recover");
localStorage.removeItem("recover_login");
save();
}
// Restore unsaved data which didn't get saved due to error
else if (localStorage.getItem("recover")) {
showMessage("We have detected that you did not save your last work. Don't worry we have recovered them. Access them using Project->Recover")
}
}, 1000);
}
// Helper function to recover unsaved data
function recoverProject() {
if (localStorage.getItem("recover")) {
var data = JSON.parse(localStorage.getItem("recover"));
if (confirm("Would you like to recover: " + data.name)) {
load(data);
}
localStorage.removeItem("recover");
} else {
showError("No recover project found")
}
}
// Toggle light mode
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();
}
//to resize window and setup things
function resetup() {
DPR = window.devicePixelRatio || 1;
if (lightMode)
DPR = 1;
width = document.getElementById("simulationArea").clientWidth * DPR;
if (!embed) {
height = (document.getElementById("simulation").clientHeight - document.getElementById("toolbar").clientHeight) * DPR;
} else {
height = (document.getElementById("simulation").clientHeight) * DPR;
}
//setup simulationArea
backgroundArea.setup();
if (!embed) plotArea.setup();
simulationArea.setup();
// update();
dots();
document.getElementById("backgroundArea").style.height = height / DPR + 100;
document.getElementById("backgroundArea").style.width = width / DPR + 100;
document.getElementById("canvasArea").style.height = height / DPR;
simulationArea.canvas.width = width;
simulationArea.canvas.height = height;
backgroundArea.canvas.width = width + 100 * DPR;
backgroundArea.canvas.height = height + 100 * DPR;
if (!embed) {
plotArea.c.width = document.getElementById("plot").clientWidth;
plotArea.c.height = document.getElementById("plot").clientHeight
}
updateCanvas = true;
update(); // INEFFICIENT, needs to be deprecated
simulationArea.prevScale = 0;
dots(true, false);
}
window.onresize = resetup; // listener
window.onorientationchange = resetup; // listener
//for mobiles
window.addEventListener('orientationchange', resetup); // listener
// Object that holds data and objects of grid canvas
var backgroundArea = {
canvas: document.getElementById("backgroundArea"),
setup: function() {
this.canvas.width = width;
this.canvas.height = height;
this.context = this.canvas.getContext("2d");
dots(true, false);
},
clear: function() {
if (!this.context) return;
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
//simulation environment object - holds simulation canvas
var simulationArea = {
canvas: document.getElementById("simulationArea"),
selected: false,
hover: false,
clockState: 0,
clockEnabled: true,
lastSelected: undefined,
stack: [],
prevScale: 0,
oldx: 0,
oldy: 0,
objectList: [],
maxHeight: 0,
maxWidth: 0,
minHeight: 0,
minWidth: 0,
multipleObjectSelections: [],
copyList: [],
shiftDown: false,
controlDown: false,
timePeriod: 500,
mouseX: 0,
mouseY: 0,
mouseDownX: 0,
mouseDownY: 0,
simulationQueue: undefined,
clickCount: 0, //double click
lock: "unlocked",
timer: function() {
ckickTimer = setTimeout(function() {
simulationArea.clickCount = 0;
}, 600);
},
setup: function() {
this.canvas.width = width;
this.canvas.height = height;
this.simulationQueue = new EventQueue(10000);
this.context = this.canvas.getContext("2d");
simulationArea.changeClockTime(simulationArea.timePeriod)
this.mouseDown = false;
},
changeClockTime: function(t) {
if (t < 50) return;
clearInterval(simulationArea.ClockInterval);
t = t || prompt("Enter Time Period:");
simulationArea.timePeriod = t;
simulationArea.ClockInterval = setInterval(clockTick, t);
},
clear: function() {
if (!this.context) return;
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
changeClockTime = simulationArea.changeClockTime
// Kept for archival purposes - needs to be removed
//
// function copyPaste(copyList) {
// if(copyList.length==0)return;
// tempScope = new Scope(globalScope.name,globalScope.id);
// var oldOx=globalScope.ox;
// var oldOy=globalScope.oy;
// var oldScale=globalScope.scale;
// d = backUp(globalScope);
// loadScope(tempScope, d);
// scopeList[tempScope.id]=tempScope;
// tempScope.backups=globalScope.backups;
// for (var i = 0; i < updateOrder.length; i++){
// var prevLength=globalScope[updateOrder[i]].length; // LOL length of list will reduce automatically when deletion starts
// // if(globalScope[updateOrder[i]].length)////console.log("deleting, ",globalScope[updateOrder[i]]);
// for (var j = 0; j < globalScope[updateOrder[i]].length; j++) {
// var obj = globalScope[updateOrder[i]][j];
// if (obj.objectType != 'Wire') { //}&&obj.objectType!='CircuitElement'){//}&&(obj.objectType!='Node'||obj.type==2)){
// if (!copyList.contains(globalScope[updateOrder[i]][j])) {
// ////console.log("DELETE:", globalScope[updateOrder[i]][j]);
// globalScope[updateOrder[i]][j].cleanDelete();
// }
// }
//
// if(globalScope[updateOrder[i]].length!=prevLength){
// prevLength--;
// j--;
// }
// }
// }
//
// // updateSimulation = true;
// // update(globalScope);
// ////console.log("DEBUG1",globalScope.wires.length)
// var prevLength=globalScope.wires.length;
// for (var i = 0; i < globalScope.wires.length; i++) {
// globalScope.wires[i].checkConnections();
// if(globalScope.wires.length!=prevLength){
// prevLength--;
// i--;
// }
// }
// ////console.log(globalScope.wires,globalScope.allNodes)
// ////console.log("DEBUG2",globalScope.wires.length)
// // update(globalScope);
// // ////console.log(globalScope.wires.length)
//
// var approxX=0;
// var approxY=0;
// for (var i = 0; i < copyList.length; i++) {
// approxX+=copyList[i].x;
// approxY+=copyList[i].y;
// }
// approxX/=copyList.length;
// approxY/=copyList.length;
//
// approxX=Math.round(approxX/10)*10
// approxY=Math.round(approxY/10)*10
// for (var i = 0; i < updateOrder.length; i++)
// for (var j = 0; j < globalScope[updateOrder[i]].length; j++) {
// var obj = globalScope[updateOrder[i]][j];
// obj.updateScope(tempScope);
// }
//
//
// for (var i = 0; i < copyList.length; i++) {
// // ////console.log(copyList[i]);
// copyList[i].x += simulationArea.mouseX-approxX;
// copyList[i].y += simulationArea.mouseY-approxY;
//
// }
//
//
// // for (var i = 0; i < globalScope.wires.length; i++) {
// // globalScope.wires[i].updateScope(tempScope);
// // }
//
// for (l in globalScope) {
// if (globalScope[l] instanceof Array && l != "objects") {
// tempScope[l].extend(globalScope[l]);
// // ////console.log("Copying , ",l);
// }
// }
//
// // update(tempScope);
//
//
// simulationArea.multipleObjectSelections = [];//copyList.slice();
// simulationArea.copyList = [];//copyList.slice();
// canvasUpdate=true;
// updateSimulation = true;
// globalScope = tempScope;
// scheduleUpdate();
// globalScope.ox=oldOx;
// globalScope.oy=oldOy;
// globalScope.scale=oldScale;
//
// // }
// paste function
function paste(copyData) {
if (copyData == undefined) return;
var data = JSON.parse(copyData);
if (!data["logixClipBoardData"]) return;
var currentScopeId = globalScope.id;
for (var i = 0; i < data.scopes.length; i++) {
if (scopeList[data.scopes[i].id] == undefined) {
var scope = newCircuit(data.scopes[i].name, data.scopes[i].id);
loadScope(scope, data.scopes[i]);
scopeList[data.scopes[i].id] = scope;
}
}
switchCircuit(currentScopeId);
var tempScope = new Scope(globalScope.name, globalScope.id);
var oldOx = globalScope.ox;
var oldOy = globalScope.oy;
var oldScale = globalScope.scale;
loadScope(tempScope, data);
var prevLength = tempScope.allNodes.length
for (var i = 0; i < tempScope.allNodes.length; i++) {
tempScope.allNodes[i].checkDeleted();
if (tempScope.allNodes.length != prevLength) {
prevLength--;
i--;
}
}
var approxX = 0;
var approxY = 0;
var count = 0
for (var i = 0; i < updateOrder.length; i++) {
for (var j = 0; j < tempScope[updateOrder[i]].length; j++) {
var obj = tempScope[updateOrder[i]][j];
obj.updateScope(globalScope);
if (obj.objectType != "Wire") {
approxX += obj.x;
approxY += obj.y;
count++;
}
}
}
for (var j = 0; j < tempScope.CircuitElement.length; j++) {
var obj = tempScope.CircuitElement[j]
obj.updateScope(globalScope);
}
approxX /= count
approxY /= count
approxX = Math.round(approxX / 10) * 10
approxY = Math.round(approxY / 10) * 10
for (var i = 0; i < updateOrder.length; i++) {
for (var j = 0; j < tempScope[updateOrder[i]].length; j++) {
var obj = tempScope[updateOrder[i]][j];
if (obj.objectType != "Wire") {
obj.x += simulationArea.mouseX - approxX;
obj.y += simulationArea.mouseY - approxY;
}
}
}
for (l in tempScope) {
if (tempScope[l] instanceof Array && l != "objects" && l != "CircuitElement") {
globalScope[l].extend(tempScope[l]);
}
}
for (var i = 0; i < tempScope.Input.length; i++) {
tempScope.Input[i].layoutProperties.y = get_next_position(0, globalScope);
tempScope.Input[i].layoutProperties.id = generateId();
}
for (var i = 0; i < tempScope.Output.length; i++) {
tempScope.Output[i].layoutProperties.x = globalScope.layout.width;
tempScope.Output[i].layoutProperties.id = generateId();
tempScope.Output[i].layoutProperties.y = get_next_position(globalScope.layout.width, globalScope);
}
canvasUpdate = true;
updateSimulation = true;
updateSubcircuit = true;
scheduleUpdate();
globalScope.ox = oldOx;
globalScope.oy = oldOy;
globalScope.scale = oldScale;
forceResetNodes = true
}
function cut(copyList) {
if (copyList.length == 0) return;
var tempScope = new Scope(globalScope.name, globalScope.id);
var oldOx = globalScope.ox;
var oldOy = globalScope.oy;
var oldScale = globalScope.scale;
d = backUp(globalScope);
loadScope(tempScope, d);
scopeList[tempScope.id] = tempScope;
for (var i = 0; i < copyList.length; i++) {
var obj = copyList[i];
if (obj.objectType == "Node") obj.objectType = "allNodes"
for (var j = 0; j < tempScope[obj.objectType].length; j++) {
if (tempScope[obj.objectType][j].x == obj.x && tempScope[obj.objectType][j].y == obj.y && (obj.objectType != "Node" || obj.type == 2)) {
tempScope[obj.objectType][j].delete();
break;
}
}
}
tempScope.backups = globalScope.backups;
for (var i = 0; i < updateOrder.length; i++) {
var prevLength = globalScope[updateOrder[i]].length; // LOL length of list will reduce automatically when deletion starts
for (var j = 0; j < globalScope[updateOrder[i]].length; j++) {
var obj = globalScope[updateOrder[i]][j];
if (obj.objectType != 'Wire') { //}&&obj.objectType!='CircuitElement'){//}&&(obj.objectType!='Node'||obj.type==2)){
if (!copyList.contains(globalScope[updateOrder[i]][j])) {
globalScope[updateOrder[i]][j].cleanDelete();
}
}
if (globalScope[updateOrder[i]].length != prevLength) {
prevLength--;
j--;
}
}
}
var prevLength = globalScope.wires.length;
for (var i = 0; i < globalScope.wires.length; i++) {
globalScope.wires[i].checkConnections();
if (globalScope.wires.length != prevLength) {
prevLength--;
i--;
}
}
updateSimulation = true;
var data = backUp(globalScope);
data['logixClipBoardData'] = true;
var dependencyList = globalScope.getDependencies();
data["dependencies"] = {};
for (dependency in dependencyList)
data.dependencies[dependency] = backUp(scopeList[dependency]);
data['logixClipBoardData'] = true;
data = JSON.stringify(data);
simulationArea.multipleObjectSelections = []; //copyList.slice();
simulationArea.copyList = []; //copyList.slice();
canvasUpdate = true;
updateSimulation = true;
globalScope = tempScope;
scheduleUpdate();
globalScope.ox = oldOx;
globalScope.oy = oldOy;
globalScope.scale = oldScale;
forceResetNodes = true
return data;
}
function copy(copyList, cut = false) {
if (copyList.length == 0) return;
var tempScope = new Scope(globalScope.name, globalScope.id);
var oldOx = globalScope.ox;
var oldOy = globalScope.oy;
var oldScale = globalScope.scale;
var d = backUp(globalScope);
loadScope(tempScope, d);
scopeList[tempScope.id] = tempScope;
if (cut) {
for (var i = 0; i < copyList.length; i++) {
var obj = copyList[i];
if (obj.objectType == "Node") obj.objectType = "allNodes"
for (var j = 0; j < tempScope[obj.objectType].length; j++) {
if (tempScope[obj.objectType][j].x == obj.x && tempScope[obj.objectType][j].y == obj.y && (obj.objectType != "Node" || obj.type == 2)) {
tempScope[obj.objectType][j].delete();
break;
}
}
}
}
tempScope.backups = globalScope.backups;
for (var i = 0; i < updateOrder.length; i++) {
var prevLength = globalScope[updateOrder[i]].length; // LOL length of list will reduce automatically when deletion starts
for (var j = 0; j < globalScope[updateOrder[i]].length; j++) {
var obj = globalScope[updateOrder[i]][j];
if (obj.objectType != 'Wire') { //}&&obj.objectType!='CircuitElement'){//}&&(obj.objectType!='Node'||obj.type==2)){
if (!copyList.contains(globalScope[updateOrder[i]][j])) {
////console.log("DELETE:", globalScope[updateOrder[i]][j]);
globalScope[updateOrder[i]][j].cleanDelete();
}
}
if (globalScope[updateOrder[i]].length != prevLength) {
prevLength--;
j--;
}
}
}
var prevLength = globalScope.wires.length;
for (var i = 0; i < globalScope.wires.length; i++) {
globalScope.wires[i].checkConnections();
if (globalScope.wires.length != prevLength) {
prevLength--;
i--;
}
}
updateSimulation = true;
var data = backUp(globalScope);
data.scopes = []
var dependencyList = {};
var requiredDependencies = globalScope.getDependencies();
var completed = {};
for (id in scopeList)
dependencyList[id] = scopeList[id].getDependencies();
function saveScope(id) {
if (completed[id]) return;
for (var i = 0; i < dependencyList[id].length; i++)
saveScope(dependencyList[id][i]);
completed[id] = true;
data.scopes.push(backUp(scopeList[id]));
}
for (var i = 0; i < requiredDependencies.length; i++)
saveScope(requiredDependencies[i]);
data['logixClipBoardData'] = true;
data = JSON.stringify(data);
simulationArea.multipleObjectSelections = []; //copyList.slice();
simulationArea.copyList = []; //copyList.slice();
canvasUpdate = true;
updateSimulation = true;
globalScope = tempScope;
scheduleUpdate();
globalScope.ox = oldOx;
globalScope.oy = oldOy;
globalScope.scale = oldScale;
forceResetNodes = true
return data;
}
// Function selects all the elements from the scope
function selectAll(scope = globalScope) {
circuitElementList.forEach((val, _, __) => {
if (scope.hasOwnProperty(val)) {
simulationArea.multipleObjectSelections.push(...scope[val]);
}
});
if (scope.nodes) {
simulationArea.multipleObjectSelections.push(...scope.nodes);
}
}
// The Circuit element class serves as the abstract class for all circuit elements.
// Data Members: /* Insert Description */
// Prototype Methods:
// - update: Used to update the state of object on mouse applicationCache
// - isHover: Used to check if mouse is hovering over object
function CircuitElement(x, y, scope, dir, bitWidth) {
// Data member initializations
this.objectType = this.constructor.name;
this.x = x;
this.y = y;
this.hover = false;
if (this.x == undefined || this.y == undefined) {
this.x = simulationArea.mouseX;
this.y = simulationArea.mouseY;
this.newElement = true;
this.hover = true;
}
this.deleteNodesWhenDeleted = true; // FOR NOW - TO CHECK LATER
this.nodeList = []
this.clicked = false;
this.oldx = x;
this.oldy = y;
/**
The following attributes help in setting the touch area bound. They are the distances from the center.
Note they are all positive distances from center. They will automatically be rotated when direction is changed.
To stop the rotation when direction is changed, check overrideDirectionRotation attribute.
**/
this.leftDimensionX = 10;
this.rightDimensionX = 10;
this.upDimensionY = 10;
this.downDimensionY = 10;
this.rectangleObject = true;
this.label = "";
this.scope = scope;
this.scope[this.objectType].push(this);
this.bitWidth = bitWidth || parseInt(prompt("Enter bitWidth"), 10) || 1;
this.direction = dir;
this.directionFixed = false;
this.labelDirectionFixed = false;
this.labelDirection = oppositeDirection[dir];
this.orientationFixed = true;
this.fixedBitWidth = false;
scheduleUpdate();
this.queueProperties = {
inQueue: false,
time: undefined,
index: undefined,
}
}
CircuitElement.prototype.alwaysResolve = false
CircuitElement.prototype.propagationDelay = 100
CircuitElement.prototype.flipBits = function(val) {
return ((~val >>> 0) << (32 - this.bitWidth)) >>> (32 - this.bitWidth);
}
CircuitElement.prototype.absX = function() {
return this.x;
}
CircuitElement.prototype.absY = function() {
return this.y;
}
CircuitElement.prototype.copyFrom = function(obj) {
var properties = ["label", "labelDirection"];
for (var i = 0; i < properties.length; i++) {
if (obj[properties[i]] !== undefined)
this[properties[i]] = obj[properties[i]];
}
}
/* Methods to be Implemented for derivedClass
saveObject(); //To generate JSON-safe data that can be loaded
customDraw(); //This is to draw the custom design of the circuit(Optional)
resolve(); // To execute digital logic(Optional)
override isResolvable(); // custom logic for checking if module is ready
override newDirection(dir) //To implement custom direction logic(Optional)
newOrientation(dir) //To implement custom orientation logic(Optional)
*/
// Method definitions
CircuitElement.prototype.updateScope = function(scope) {
this.scope = scope;
for (var i = 0; i < this.nodeList.length; i++)
this.nodeList[i].scope = scope;
}
CircuitElement.prototype.saveObject = function() {
var data = {
x: this.x,
y: this.y,
objectType: this.objectType,
label: this.label,
direction: this.direction,
labelDirection: this.labelDirection,
propagationDelay: this.propagationDelay,
customData: this.customSave()
}
return data;
}
CircuitElement.prototype.customSave = function() {
return {
values: {},
nodes: {},
constructorParamaters: [],
}
}
CircuitElement.prototype.checkHover = function() {
if (simulationArea.mouseDown) return;
for (var i = 0; i < this.nodeList.length; i++) {
this.nodeList[i].checkHover();
}
if (!simulationArea.mouseDown) {
if (simulationArea.hover == this) {
this.hover = this.isHover();
if (!this.hover) simulationArea.hover = undefined;
} else if (!simulationArea.hover) {
this.hover = this.isHover();
if (this.hover) simulationArea.hover = this;
} else {
this.hover = false
}
}
}
//This sets the width and height of the element if its rectangular
// and the reference point is at the center of the object.
//width and height define the X and Y distance from the center.
//Effectively HALF the actual width and height.
// NOT OVERRIDABLE
CircuitElement.prototype.setDimensions = function(width, height) {
this.leftDimensionX = this.rightDimensionX = width;
this.downDimensionY = this.upDimensionY = height;
}
CircuitElement.prototype.setWidth = function(width) {
this.leftDimensionX = this.rightDimensionX = width;
}
CircuitElement.prototype.setHeight = function(height) {
this.downDimensionY = this.upDimensionY = height;
}
// The update method is used to change the parameters of the object on mouse click and hover.
// Return Value: true if state has changed else false
// NOT OVERRIDABLE
// When true this.isHover() will not rotate bounds. To be used when bounds are set manually.
CircuitElement.prototype.overrideDirectionRotation = false;
CircuitElement.prototype.startDragging = function() {
this.oldx = this.x;
this.oldy = this.y;
}
CircuitElement.prototype.drag = function() {
this.x = this.oldx + simulationArea.mouseX - simulationArea.mouseDownX;
this.y = this.oldy + simulationArea.mouseY - simulationArea.mouseDownY;
}
CircuitElement.prototype.update = function() {
var update = false;
update |= this.newElement;
if (this.newElement) {
if (this.centerElement) {
this.x = Math.round((simulationArea.mouseX - (this.rightDimensionX - this.leftDimensionX) / 2) / 10) * 10;
this.y = Math.round((simulationArea.mouseY - (this.downDimensionY - this.upDimensionY) / 2) / 10) * 10;
} else {
this.x = simulationArea.mouseX;
this.y = simulationArea.mouseY;
}
if (simulationArea.mouseDown) {
this.newElement = false;
simulationArea.lastSelected = this;
} else return;
}
for (var i = 0; i < this.nodeList.length; i++) {
update |= this.nodeList[i].update();
}
if (!simulationArea.hover || simulationArea.hover == this)
this.hover = this.isHover();
if (!simulationArea.mouseDown) this.hover = false;
if ((this.clicked || !simulationArea.hover) && this.isHover()) {
this.hover = true;
simulationArea.hover = this;
} else if (!simulationArea.mouseDown && this.hover && this.isHover() == false) {
if (this.hover) simulationArea.hover = undefined;
this.hover = false;
}
if (simulationArea.mouseDown && (this.clicked)) {
this.drag();
if (!simulationArea.shiftDown && simulationArea.multipleObjectSelections.contains(this)) {
for (var i = 0; i < simulationArea.multipleObjectSelections.length; i++) {
simulationArea.multipleObjectSelections[i].drag();
}
}
update |= true;
} else if (simulationArea.mouseDown && !simulationArea.selected) {
this.startDragging();
if (!simulationArea.shiftDown && simulationArea.multipleObjectSelections.contains(this)) {
for (var i = 0; i < simulationArea.multipleObjectSelections.length; i++) {
simulationArea.multipleObjectSelections[i].startDragging();
}
}
simulationArea.selected = this.clicked = this.hover;
update |= this.clicked;
} else {
if (this.clicked) simulationArea.selected = false;
this.clicked = false;
this.wasClicked = false;
}
if (simulationArea.mouseDown && !this.wasClicked) {
if (this.clicked) {
this.wasClicked = true;
if (this.click) this.click();
if (simulationArea.shiftDown) {
simulationArea.lastSelected = undefined;
if (simulationArea.multipleObjectSelections.contains(this)) {
simulationArea.multipleObjectSelections.clean(this);
} else {
simulationArea.multipleObjectSelections.push(this);
}
} else {
simulationArea.lastSelected = this;
}
}
}
return update;
}
CircuitElement.prototype.fixDirection = function() {
this.direction = fixDirection[this.direction] || this.direction;
this.labelDirection = fixDirection[this.labelDirection] || this.labelDirection;
}
// The isHover method is used to check if the mouse is hovering over the object.
// Return Value: true if mouse is hovering over object else false
// NOT OVERRIDABLE
CircuitElement.prototype.isHover = function() {
var mX = simulationArea.mouseXf - this.x;
var mY = this.y - simulationArea.mouseYf;
var rX = this.rightDimensionX;
var lX = this.leftDimensionX;
var uY = this.upDimensionY;
var dY = this.downDimensionY;
if (!this.directionFixed && !this.overrideDirectionRotation) {
if (this.direction == "LEFT") {
lX = this.rightDimensionX;
rX = this.leftDimensionX
} else if (this.direction == "DOWN") {
lX = this.downDimensionY;
rX = this.upDimensionY;
uY = this.leftDimensionX;
dY = this.rightDimensionX;
} else if (this.direction == "UP") {
lX = this.downDimensionY;
rX = this.upDimensionY;
dY = this.leftDimensionX;
uY = this.rightDimensionX;
}
}
return -lX <= mX && mX <= rX && -dY <= mY && mY <= uY;
};
CircuitElement.prototype.setLabel = function(label) {
this.label = label || ""
}
CircuitElement.prototype.propagationDelayFixed = false;
//Method that draws the outline of the module and calls draw function on module Nodes.
//NOT OVERRIDABLE
CircuitElement.prototype.draw = function() {
var ctx = simulationArea.context;
this.checkHover();
if (this.x * this.scope.scale + this.scope.ox < -this.rightDimensionX * this.scope.scale - 00 || this.x * this.scope.scale + this.scope.ox > width + this.leftDimensionX * this.scope.scale + 00 || this.y * this.scope.scale + this.scope.oy < -this.downDimensionY * this.scope.scale - 00 || this.y * this.scope.scale + this.scope.oy > height + 00 + this.upDimensionY * this.scope.scale) return;
// Draws rectangle and highlights
if (this.rectangleObject) {
ctx.strokeStyle = "black";
ctx.fillStyle = "white";
ctx.lineWidth = correctWidth(3);
ctx.beginPath();
rect2(ctx, -this.leftDimensionX, -this.upDimensionY, this.leftDimensionX + this.rightDimensionX, this.upDimensionY + this.downDimensionY, this.x, this.y, [this.direction, "RIGHT"][+this.directionFixed]);
if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)";
ctx.fill();
ctx.stroke();
}
if (this.label != "") {
var rX = this.rightDimensionX;
var lX = this.leftDimensionX;
var uY = this.upDimensionY;
var dY = this.downDimensionY;
if (!this.directionFixed) {
if (this.direction == "LEFT") {
lX = this.rightDimensionX;
rX = this.leftDimensionX
} else if (this.direction == "DOWN") {
lX = this.downDimensionY;
rX = this.upDimensionY;
uY = this.leftDimensionX;
dY = this.rightDimensionX;
} else if (this.direction == "UP") {
lX = this.downDimensionY;
rX = this.upDimensionY;
dY = this.leftDimensionX;
uY = this.rightDimensionX;
}
}
if (this.labelDirection == "LEFT") {
ctx.beginPath();
ctx.textAlign = "right";
ctx.fillStyle = "black";
fillText(ctx, this.label, this.x - lX - 10, this.y + 5, 14);
ctx.fill();
} else if (this.labelDirection == "RIGHT") {
ctx.beginPath();
ctx.textAlign = "left";
ctx.fillStyle = "black";
fillText(ctx, this.label, this.x + rX + 10, this.y + 5, 14);
ctx.fill();
} else if (this.labelDirection == "UP") {
ctx.beginPath();
ctx.textAlign = "center";
ctx.fillStyle = "black";
fillText(ctx, this.label, this.x, this.y + 5 - uY - 10, 14);
ctx.fill();
} else if (this.labelDirection == "DOWN") {
ctx.beginPath();
ctx.textAlign = "center";
ctx.fillStyle = "black";
fillText(ctx, this.label, this.x, this.y + 5 + dY + 10, 14);
ctx.fill();
}
}
// calls the custom circuit design
if (this.customDraw)
this.customDraw();
//draws nodes - Moved to renderCanvas
// for (var i = 0; i < this.nodeList.length; i++)
// this.nodeList[i].draw();
}
//method to delete object
//OVERRIDE WITH CAUTION
CircuitElement.prototype.delete = function() {
simulationArea.lastSelected = undefined;
this.scope[this.objectType].clean(this); // CHECK IF THIS IS VALID
if (this.deleteNodesWhenDeleted)
this.deleteNodes();
else
for (var i = 0; i < this.nodeList.length; i++)
if (this.nodeList[i].connections.length)
this.nodeList[i].converToIntermediate();
else
this.nodeList[i].delete();
this.deleted = true;
}
CircuitElement.prototype.cleanDelete = function() {
this.deleteNodesWhenDeleted = true;
this.delete();
}
CircuitElement.prototype.deleteNodes = function() {
for (var i = 0; i < this.nodeList.length; i++)
this.nodeList[i].delete();
}
//method to change direction
//OVERRIDE WITH CAUTION
CircuitElement.prototype.newDirection = function(dir) {
if (this.direction == dir) return;
// Leave this for now
if (this.directionFixed && this.orientationFixed) return;
else if (this.directionFixed) {
this.newOrientation(dir);
return; // Should it return ?
}
// if (obj.direction == undefined) return;
this.direction = dir;
for (var i = 0; i < this.nodeList.length; i++) {
this.nodeList[i].refresh();
}
}
CircuitElement.prototype.newLabelDirection = function(dir) {
this.labelDirection = dir;
}
//Method to check if object can be resolved
//OVERRIDE if necessary
CircuitElement.prototype.isResolvable = function() {
if (this.alwaysResolve) return true;
for (var i = 0; i < this.nodeList.length; i++)
if (this.nodeList[i].type == 0 && this.nodeList[i].value == undefined) return false;
return true;
}
//Method to change object Bitwidth
//OVERRIDE if necessary
CircuitElement.prototype.newBitWidth = function(bitWidth) {
if (this.fixedBitWidth) return;
if (this.bitWidth == undefined) return;
if (this.bitWidth < 1) return;
this.bitWidth = bitWidth;
for (var i = 0; i < this.nodeList.length; i++)
this.nodeList[i].bitWidth = bitWidth;
}
//Method to change object delay
//OVERRIDE if necessary
CircuitElement.prototype.changePropagationDelay = function(delay) {
if (this.propagationDelayFixed) return;
if (delay == undefined) return;
if (delay == "") return;
delay = parseInt(delay, 10)
if (delay < 0) return;
this.propagationDelay = delay;
}
//Dummy resolve function
//OVERRIDE if necessary
CircuitElement.prototype.resolve = function() {
}
// Graph algorithm to resolve verilog wire labels
CircuitElement.prototype.processVerilog = function() {
// Output count used to sanitize output
var output_total = 0;
for (var i = 0; i < this.nodeList.length; i++) {
if (this.nodeList[i].type == NODE_OUTPUT && this.nodeList[i].connections.length > 0)
output_total++;
}
var output_count = 0;
for (var i = 0; i < this.nodeList.length; i++) {
if (this.nodeList[i].type == NODE_OUTPUT) {
if (this.objectType != "Input" && this.objectType != "Clock" && this.nodeList[i].connections.length > 0) {
this.nodeList[i].verilogLabel =
verilog.generateNodeName(this.nodeList[i], output_count, output_total);
if (!this.scope.verilogWireList[this.nodeList[i].bitWidth].contains(this.nodeList[i].verilogLabel))
this.scope.verilogWireList[this.nodeList[i].bitWidth].push(this.nodeList[i].verilogLabel);
output_count++;
}
this.scope.stack.push(this.nodeList[i]);
}
}
}
CircuitElement.prototype.removePropagation = function() {
for (var i = 0; i < this.nodeList.length; i++) {
if (this.nodeList[i].type == NODE_OUTPUT) {
if (this.nodeList[i].value !== undefined) {
this.nodeList[i].value = undefined;
simulationArea.simulationQueue.add(this.nodeList[i]);
}
}
}
}
CircuitElement.prototype.verilogName = function() {
return this.verilogType || this.objectType;
}
CircuitElement.prototype.verilogBaseType = function() {
return this.verilogName();
}
CircuitElement.prototype.verilogParametrizedType = function() {
var type = this.verilogBaseType();
// Suffix bitwidth for multi-bit inputs
// Example: DflipFlop #(2) DflipFlop_0
if (this.bitWidth != undefined && this.bitWidth > 1)
type += " #(" + this.bitWidth + ")";
return type
}
// Generates final verilog code for each element
CircuitElement.prototype.generateVerilog = function() {
// Example: and and_1(_out, _out, _Q[0]);
var inputs = [];
var outputs = [];
for (var i = 0; i < this.nodeList.length; i++) {
if (this.nodeList[i].type == NODE_INPUT) {
inputs.push(this.nodeList[i]);
} else {
if (this.nodeList[i].connections.length > 0)
outputs.push(this.nodeList[i]);
else
outputs.push(""); // Don't create a wire
}
}
var list = outputs.concat(inputs);
var res = this.verilogParametrizedType();
var moduleParams = list.map(x => x.verilogLabel).join(", ");
res += ` ${this.verilogLabel}(${moduleParams});`;
return res;
}
// Generates final verilog code for each element
// Gate = &/|/^
// Invert is true for xNor, Nor, Nand
function gateGenerateVerilog(gate, invert = false) {
var inputs = [];
var outputs = [];
for (var i = 0; i < this.nodeList.length; i++) {
if (this.nodeList[i].type == NODE_INPUT) {
inputs.push(this.nodeList[i]);
} else {
if (this.nodeList[i].connections.length > 0)
outputs.push(this.nodeList[i]);
else
outputs.push(""); // Don't create a wire
}
}
var res = "assign ";
if (outputs.length == 1)
res += outputs[0].verilogLabel;
else
res += `{${outputs.map(x => x.verilogLabel).join(", ")}}`;
res += " = ";
var inputParams = inputs.map(x => x.verilogLabel).join(` ${gate} `);
if(invert) {
res += `~(${inputParams});`;
}
else {
res += inputParams + ';';
}
return res;
}
function distance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2));
}