Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes to change bits after initialization #31

Merged
merged 3 commits into from
Sep 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions examples/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div>
<button name="start" type="button">▶️</button>
<button name="stop" type="button">⏹️</button>
</div>
<div id="paper">
</div>
<div>
Expand All @@ -21,27 +25,43 @@
</div>
<script>
var circuit, monitor, monitorview, iopanel, paper;
var subpapers = {};
var start = $('button[name=start]');
var stop = $('button[name=stop]');
var papers = {};
const fixed = function (fixed) {
paper.fixed(fixed);
Object.values(subpapers).forEach(p => p.fixed(fixed));
Object.values(papers).forEach(p => p.fixed(fixed));
}
const loadCircuit = function (json) {
circuit = new digitaljs.Circuit(json);
monitor = new digitaljs.Monitor(circuit);
monitorview = new digitaljs.MonitorView({model: monitor, el: $('#monitor') });
iopanel = new digitaljs.IOPanelView({model: circuit, el: $('#iopanel') });
circuit.on('new:paper', function(paper) {
paper.fixed($('input[name=fixed]').prop('checked'));
papers[paper.cid] = paper;
paper.on('element:pointerdblclick', (cellView) => {
window.digitaljsCell = cellView.model;
console.info('You can now access the doubly clicked gate as digitaljsCell in your WebBrowser console!');
});
});
circuit.on('changeRunning', () => {
if (circuit.running) {
start.prop('disabled', true);
stop.prop('disabled', false);
} else {
start.prop('disabled', false);
stop.prop('disabled', true);
}
});
paper = circuit.displayOn($('#paper'));
fixed($('input[name=fixed]').prop('checked'));
circuit.on('new:paper', function(subpaper) {
subpaper.fixed($('input[name=fixed]').prop('checked'));
subpapers[subpaper.cid] = subpaper;
});
circuit.on('remove:paper', function(subpaper) {
delete subpapers[subpaper.cid];
circuit.on('remove:paper', function(paper) {
delete papers[paper.cid];
});
circuit.start();
}
start.on('click', (e) => { circuit.start(); });
stop.on('click', (e) => { circuit.stop(); });
$('button[name=json]').on('click', (e) => {
monitorview.shutdown();
iopanel.shutdown();
Expand Down
105 changes: 67 additions & 38 deletions src/cells/base.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,14 @@ export const Gate = joint.shapes.basic.Generic.define('Gate', {

this.bindAttrToProp('label/text', 'label');
if (this.unsupportedPropChanges.length > 0) {
this.on(this.unsupportedPropChanges.map(prop => 'change:'+prop).join(' '), function(model, _, opt) {
this.on(this.unsupportedPropChanges.map(prop => 'change:'+prop).join(' '), (__, ___, opt) => {
if (opt.init) return;

if (opt.propertyPath)
console.warn('Beta property change support: "' + opt.propertyPath + '" changes on ' + model.prop('type') + ' are currently not reflected.');
else
console.warn('Beta property change support: changes on ' + model.prop('type') + ' are currently not reflected. Also consider using Cell.prop() instead of Model.set().');
const changed = _.intersection(Object.keys(this.changed), this.unsupportedPropChanges);
changed.forEach(attr => {
this.set(attr, this.previous(attr), {init: true});
});
console.warn('Beta property change support: "' + changed + '" changes on ' + this.get('type') + ' are (currently) not supported.');
});
}
},
Expand All @@ -127,11 +128,8 @@ export const Gate = joint.shapes.basic.Generic.define('Gate', {
this.on('change:' + prop, (_, val) => this.attr(attr, val));
},
_preprocessPorts: function(ports) {
this.resetPortsSignals(ports);
for (const port of ports) {
const signame = port.dir === 'in' ? 'inputSignals' : 'outputSignals';
this.get(signame)[port.id] = Vector3vl.xes(port.bits);
console.assert(port.bits > 0);

port.attrs = {};
port.attrs['bits'] = { text: this.getBitsText(port.bits) }
if (port.labelled) {
Expand All @@ -150,34 +148,56 @@ export const Gate = joint.shapes.basic.Generic.define('Gate', {
}
},
setPortsBits: function(portsBits) {
const ports = this.get('ports');
const ports = _.cloneDeep(this.get('ports'));
const portsReset = [];
for (const portid in portsBits) {
const bits = portsBits[portid];
const port = ports.items.find(function(port) {
return port.id && port.id === portid;
});
port.bits = bits;
port.attrs['bits'].text = this.getBitsText(bits);

const signame = port.dir === 'in' ? 'inputSignals' : 'outputSignals';
const signals = this.get(signame);
signals[portid] = Vector3vl.xes(bits);
this.set(signame, signals);

this.graph.getConnectedLinks(this, { outbound: port.dir === 'out', inbound: port.dir === 'in' })
.filter((wire) => wire.get(port.dir === 'out' ? 'source' : 'target').port === portid)
.forEach((wire) => wire.checkConnection());
portsReset.push(port);
}
this.resetPortsSignals(portsReset);
//trigger port changes on model and view
this.trigger('change:ports', this, ports);
this.set('ports', ports);
this.graph.getConnectedLinks(this, { outbound: true })
.filter((wire) => wire.get('source').port in portsBits)
.forEach((wire) => wire.changeSource(wire.get('source')));
this.graph.getConnectedLinks(this, { inbound: true })
.filter((wire) => wire.get('target').port in portsBits)
.forEach((wire) => wire.changeTarget(wire.get('target')));
},
getBitsText: function(bits) {
return bits > 1 ? bits : '';
},
removePortSignals: function(port) {
port = port.id !== undefined ? port : this.getPort(port);
const signame = port.dir === 'in' ? 'inputSignals' : 'outputSignals';
this.removeProp([signame, port.id]);
resetPortsSignals: function(ports) {
const signals = {
in: this.get('inputSignals'),
out: this.get('outputSignals')
}

for (const port of ports) {
console.assert(port.bits > 0);
signals[port.dir][port.id] = Vector3vl.xes(port.bits);
}

this.set('inputSignals', signals.in);
this.set('outputSignals', signals.out);
},
removePortsSignals: function(ports) {
const signals = {
in: this.get('inputSignals'),
out: this.get('outputSignals')
}

for (const port of ports) {
delete signals[port.dir][port.id];
}

this.set('inputSignals', signals.in);
this.set('outputSignals', signals.out);
},
addPort: function(port) {
this.addPorts([port]);
Expand All @@ -187,11 +207,10 @@ export const Gate = joint.shapes.basic.Generic.define('Gate', {
joint.shapes.basic.Generic.prototype.addPorts.apply(this, arguments);
},
removePort: function(port, opt) {
this.removePortSignals(port);
joint.shapes.basic.Generic.prototype.removePort.apply(this, arguments);
this.removePorts([port]);
},
removePorts: function(ports, opt) {
ports.forEach((port) => this.removePortSignals(port), this);
this.removePortsSignals(ports);
joint.shapes.basic.Generic.prototype.removePorts.apply(this, arguments);
},
getStackedPosition: function(opt) {
Expand Down Expand Up @@ -306,9 +325,11 @@ export const GateView = joint.dia.ElementView.extend({
}
},
applyPortAttrs(port, attrs) {
for (const selector in attrs) {
const node = this._portElementsCache[port].portSelectors[selector];
this.setNodeAttributes(node, attrs[selector]);
if (port in this._portElementsCache) {
for (const selector in attrs) {
const node = this._portElementsCache[port].portSelectors[selector];
this.setNodeAttributes(node, attrs[selector]);
}
}
},
_updatePorts() {
Expand Down Expand Up @@ -372,12 +393,20 @@ export const Wire = joint.shapes.standard.Link.define('Wire', {
}
joint.shapes.standard.Link.prototype.remove.apply(this, arguments);
},
changeSignal(sig) {
propagateSignal(tar, sig) {
const target = this.getTargetElement();
if (target) target.setInput(sig, this.get('target').port);
if (target) {
if (this.get('warning'))
target.clearInput(tar.port);
else
target.setInput(sig, tar.port);
}
},
changeSignal(sig) {
this.propagateSignal(this.get('target'), sig);
},
changeSource(src) {
const source = this.graph.getCell(src.id);
const source = this.getSourceElement();
if (source && 'port' in src) {
this.set('bits', source.getPort(src.port).bits);
this.checkConnection();
Expand All @@ -388,11 +417,11 @@ export const Wire = joint.shapes.standard.Link.define('Wire', {
}
},
changeTarget(tar) {
const target = this.graph.getCell(tar.id);
if (target && 'port' in tar) {
target.setInput(this.get('signal'), tar.port);
}
this.checkConnection();
if ('port' in tar) {
this.propagateSignal(tar, this.get('signal'));
}
if (!this.hasChanged('target')) return;
const preTar = this.previous('target');
const preTarget = this.graph.getCell(preTar.id);
if (preTarget && 'port' in preTar) {
Expand All @@ -402,7 +431,7 @@ export const Wire = joint.shapes.standard.Link.define('Wire', {
checkConnection() {
const tar = this.get('target');
const target = this.graph.getCell(tar.id);
this.set('warning', target && target.getPort(tar.port).bits !== this.get('bits'));
this.set('warning', (target && target.getPort(tar.port).bits !== this.get('bits')) || false);
},
getWireParams: function(layout) {
const connector = {
Expand Down
2 changes: 1 addition & 1 deletion src/cells/bus.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const BusSlice = Box.define('BusSlice', {

Box.prototype.initialize.apply(this, arguments);

this.on('change:extend', (_, extend) => {
this.on('change:slice', (_, slice) => {
this.setPortsBits({ in: slice.total, out: slice.count });
});
},
Expand Down
27 changes: 20 additions & 7 deletions src/cells/io.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,6 @@ export const NumEntry = NumBase.define('NumEntry', {
this.on('change:bits', (_, bits) => {
this.setPortsBits({ out: bits });
});

this.set('buttonState', this.get('outputSignals').out);
},
operation: function() {
return { out: this.get('buttonState') };
Expand Down Expand Up @@ -257,7 +255,8 @@ export const Lamp = Box.define('Lamp', {
]),
getLogicValue: function() {
return this.get('inputSignals').in;
}
},
unsupportedPropChanges: Box.prototype.unsupportedPropChanges.concat(['bits'])
});
export const LampView = BoxView.extend({
attrs: _.merge({}, BoxView.prototype.attrs, {
Expand Down Expand Up @@ -323,7 +322,8 @@ export const Button = Box.define('Button', {
if (sig.bits != 1)
throw new Error("setLogicValue: wrong number of bits");
this.set('buttonState', sig.isHigh);
}
},
unsupportedPropChanges: Box.prototype.unsupportedPropChanges.concat(['bits'])
});
export const ButtonView = BoxView.extend({
attrs: _.merge({}, BoxView.prototype.attrs, {
Expand Down Expand Up @@ -392,6 +392,15 @@ export const IO = Box.define('IO', {
});
this.bindAttrToProp('text.ioname/text', 'net');
},
setPortsBits: function(portsBits) {
Box.prototype.setPortsBits.apply(this, arguments);

const subcir = this.graph.get('subcircuit');
if (subcir == null) return; // not inside a subcircuit
const portsBitsSubcir = {};
portsBitsSubcir[this.get('net')] = portsBits[this.io_dir];
subcir.setPortsBits(portsBitsSubcir);
},
markup: Box.prototype.markup.concat([{
tagName: 'text',
className: 'ioname',
Expand Down Expand Up @@ -479,14 +488,17 @@ export const Constant = NumBase.define('Constant', {
numbaseType: 'show'
});
export const ConstantView = NumBaseView.extend({
presentationAttributes: NumBaseView.addPresentationAttributes({
constantCache: 'CONSTANT'
}),
confirmUpdate(flags) {
NumBaseView.prototype.confirmUpdate.apply(this, arguments);
if (this.hasFlag(flags, 'SIGNAL2') ||
if (this.hasFlag(flags, 'CONSTANT') ||
this.hasFlag(flags, 'NUMBASE')) this.settext();
},
settext() {
const display3vl = this.model.graph._display3vl;
this.$('text.value tspan').text(display3vl.show(this.model.get('numbase'), this.model.get('outputSignals').out));
this.$('text.value tspan').text(display3vl.show(this.model.get('numbase'), this.model.get('constantCache')));
},
update() {
NumBaseView.prototype.update.apply(this, arguments);
Expand Down Expand Up @@ -534,7 +546,8 @@ export const Clock = Box.define('Clock', {
}]
}]
}
])
]),
unsupportedPropChanges: Box.prototype.unsupportedPropChanges.concat(['bits'])
});
export const ClockView = BoxView.extend({
presentationAttributes: BoxView.addPresentationAttributes({
Expand Down
6 changes: 5 additions & 1 deletion src/circuit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,14 @@ export class HeadlessCircuit {
const graph = new joint.dia.Graph();
graph._display3vl = this._display3vl;
graph._warnings = 0;
this.listenTo(graph, 'change:buttonState', (gate, sig) => {
this.listenTo(graph, 'change:buttonState', (gate) => {
// buttonState is triggered for any user change on inputs
this.enqueue(gate);
this.trigger('userChange');
});
this.listenTo(graph, 'change:constantCache', (gate) => {
this.enqueue(gate);
});
this.listenTo(graph, 'change:inputSignals', (gate, sigs) => {
if (gate.changeInputSignals) {
gate.changeInputSignals(sigs);
Expand All @@ -118,6 +121,7 @@ export class HeadlessCircuit {
if (cell.previous('warning') === warn)
return;
graph._warnings += warn ? 1 : -1;
console.assert(graph._warnings >= 0);

//todo: better handling for stopping simulation
if (graph._warnings > 0 && this.running)
Expand Down