import Logic from './Logic';
/**
* @module Simulation
*/
class stateChange {
constructor(connectorId, state, whoCausedIt) {
this.connectorId = connectorId;
this.state = state;
this.whoCausedIt = whoCausedIt;
}
}
/**
* This is a dummy that does nothing, just logs the function calls.
*
* Used on networks that has not been yet simulated but user tries to change logic values.
*/
export class SimulationDummy {
notifyChange() {
console.log('SimulationDummy.notifyChange() has been called.');
}
run() {
console.log('SimulationDummy.run() has been called.');
}
}
/**
* This class runs the network simulation.
*
* _note: all connectors that are used in this class are **output connectors**_
*/
export default class Simulation {
/**
* @param {App} appInstance instance of [App](./module-App.html)
*/
constructor(appInstance) {
/**
* instance of App this Simulation belongs to
* @type {App}
*/
this.appInstance = appInstance;
/**
* maps each affected output connector to it's directly preceeding output connectors
* @type {Map}
*/
this.predecessors = new Map();
/**
* maps waveId to an array of affected outputConnectors
* @type {Map}
*/
this.waves = new Map();
this.wave = 0;
/**
* maps cycled connector id to set of states this connector was in
* @type {Map}
*/
this.cycledConnectors = new Map();
/**
* set of cycled connectors that have been already resolved
* @type {Set}
*/
this.resolvedCycledConnectors = new Set();
}
/**
* run the simulation
*/
run() {
this.wave++;
while (this.waves.has(this.wave)) {
this.step();
this.waves.delete(this.wave); // clean old waves on the go
this.wave++;
}
}
/**
* one step/wave of the simulation
*
* determines states of the connectors in the current wave, detects cycles
*/
step() {
for (let { connectorId, state, whoCausedIt } of this.waves.get(this.wave)) {
// skip resolved cycles
if (this.resolvedCycledConnectors.has(connectorId)) {
continue;
}
// skip connector that are cycles
if (this.cycledConnectors.has(connectorId)) {
// get the set of states that this connector appeared from the moment the signal first cycled
let states = this.cycledConnectors.get(connectorId);
// if the connector already had this state in this cycle, resolve the cycle
if (states.has(state)) {
// if there are more states in the set, the connector is oscillating
// (else it keeps its state and we just break the cycle)
if (states.size > 1) {
state = Logic.state.oscillating;
}
// mark this connector as resolved
this.resolvedCycledConnectors.add(connectorId);
// this is a new, unseen state, add it to the set and continue simulating the cycle
} else {
states.add(state);
}
// map the modified set of states to the connector
this.cycledConnectors.set(connectorId, states);
}
this.whoCausedIt = connectorId;
/* process all outputConnectors by setting their state
this will trigger a following event chain:
outputConnector changes
-> all connected wires change
-> all inputConnectors connected to these wires change
-> all elements that contain these inputConnectors change
-> these elements compute the new state of their output connectors and call notifyChange()
*/
if (whoCausedIt) {
this.addPredecessor(connectorId, whoCausedIt);
}
if (
!this.cycledConnectors.has(connectorId) &&
this.getAllPredecessors(connectorId).has(connectorId)
) {
this.cycledConnectors.set(connectorId, new Set([state]));
}
// reflect the changes in SVG
let connector = this.appInstance.getConnectorById(connectorId);
if (connector) {
connector.setState(state);
}
}
this.whoCausedIt = undefined;
}
/**
* mark a predecessorConnectorId as a predecessor of connectorId
* @param {string} connectorId ID of a connector
* @param {string} predecessorConnectorId predecessor of `connectorId`
*/
addPredecessor(connectorId, predecessorConnectorId) {
if (!this.predecessors.has(connectorId)) {
this.predecessors.set(connectorId, new Set());
}
this.predecessors.get(connectorId).add(predecessorConnectorId);
}
/**
* get set of all output connectors that are before this output connector
* @param {string} connectorId ID of a connector
* @return {Set} set of connector ids that are before this output connector
*/
getAllPredecessors(connectorId) {
if (!this.predecessors.has(connectorId)) {
this.predecessors.set(connectorId, new Set());
}
let all = new Set();
this.predecessors.get(connectorId).forEach(all.add, all);
let prevSize = 0;
let size = all.size;
while (prevSize < size) {
for (let connector of all) {
if (this.predecessors.has(connector)) {
this.predecessors.get(connector).forEach(all.add, all);
}
}
prevSize = size;
size = all.size;
}
return all;
}
/**
* Notify a change in the network. This function adds the changed connector to the next wave
* @param {string} connectorId ID of the changed connector
* @param {Logic.state} state new [Logic.state](./module-Logic.html#.state) of the connector
*/
notifyChange(connectorId, state) {
let waveId = this.wave + 1;
if (!this.waves.has(waveId)) {
this.waves.set(waveId, []);
}
this.waves.get(waveId).push(new stateChange(connectorId, state, this.whoCausedIt));
}
}