Skip to content

Commit bd2b236

Browse files
disable circularity trigger when an output is also an input
1 parent fd2f6e9 commit bd2b236

File tree

1 file changed

+84
-25
lines changed

1 file changed

+84
-25
lines changed

dash-renderer/src/actions/dependencies.js

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -363,32 +363,21 @@ function findDuplicateOutputs(outputs, head, dispatchError, outStrs, outObjs) {
363363
});
364364
}
365365

366-
function findInOutOverlap(outputs, inputs, head, dispatchError) {
367-
outputs.forEach((out, outi) => {
366+
function checkInOutOverlap(out, inputs) {
368367
const {id: outId, property: outProp} = out;
369-
inputs.forEach((in_, ini) => {
368+
return inputs.some(in_ => {
370369
const {id: inId, property: inProp} = in_;
371370
if (outProp !== inProp || typeof outId !== typeof inId) {
372-
return;
371+
return false;
373372
}
374373
if (typeof outId === 'string') {
375374
if (outId === inId) {
376-
dispatchError('Same `Input` and `Output`', [
377-
head,
378-
`Input ${ini} (${combineIdAndProp(in_)})`,
379-
`matches Output ${outi} (${combineIdAndProp(out)})`
380-
]);
375+
return true;
381376
}
382377
} else if (wildcardOverlap(in_, [out])) {
383-
dispatchError('Same `Input` and `Output`', [
384-
head,
385-
`Input ${ini} (${combineIdAndProp(in_)})`,
386-
'can match the same component(s) as',
387-
`Output ${outi} (${combineIdAndProp(out)})`
388-
]);
378+
return true;
389379
}
390380
});
391-
});
392381
}
393382

394383
function findMismatchedWildcards(outputs, inputs, state, head, dispatchError) {
@@ -748,15 +737,52 @@ export function computeGraphs(dependencies, dispatchError) {
748737
return idList;
749738
}
750739

740+
/* multiGraph is used only for testing circularity
741+
*
742+
* Each component+property that is used as an input or output is added as a node
743+
* to a directed graph with a dependency from each input to each output. The
744+
* function triggerDefaultState in index.js then checks this graph for circularity.
745+
*
746+
* In order to allow the same component+property to be both an input and output
747+
* of the same callback, a two pass approach is used.
748+
*
749+
* In the first pass, the graph is built up normally with the exception that
750+
* in cases where an output is also an input to the same callback a special
751+
* "output" node is added and the dependencies target this output node instead.
752+
* For example, if `slider.value` is both an input and an output, then the a new
753+
* node `slider.value__output` will be added with a dependency from `slide.value`
754+
* to `slider.value__output`. Splitting the input and output into separate nodes
755+
* removes the circularity.
756+
*
757+
* In order to still detect other forms of circularity, it is necessary to do a
758+
* second pass and add the new output nodes as a dependency in any *other* callbacks
759+
* where the original node was an input. Continuing the example, any other callback
760+
* that had `slider.value` as an input dependency also needs to have
761+
* `slider.value__output` as a dependency. To make this efficient, all the inputs
762+
* and outputs for each callback are stored during the first pass.
763+
*/
764+
765+
const outputTag = "__output";
766+
const duplicateOutputs = [];
767+
const cbIn = [];
768+
const cbOut = [];
769+
770+
function addInputToMulti(inIdProp, outIdProp, firstPass=true) {
771+
multiGraph.addNode(inIdProp);
772+
multiGraph.addDependency(inIdProp, outIdProp);
773+
// only store callback inputs and outputs during the first pass
774+
if (firstPass){
775+
cbIn[cbIn.length-1].push(inIdProp);
776+
cbOut[cbOut.length-1].push(outIdProp);
777+
}
778+
}
779+
751780
parsedDependencies.forEach(function registerDependency(dependency) {
752781
const {outputs, inputs} = dependency;
753782

754-
// multiGraph - just for testing circularity
755-
756-
function addInputToMulti(inIdProp, outIdProp) {
757-
multiGraph.addNode(inIdProp);
758-
multiGraph.addDependency(inIdProp, outIdProp);
759-
}
783+
// new callback, add an empty array for its inputs and outputs
784+
cbIn.push([]);
785+
cbOut.push([]);
760786

761787
function addOutputToMulti(outIdFinal, outIdProp) {
762788
multiGraph.addNode(outIdProp);
@@ -790,15 +816,29 @@ export function computeGraphs(dependencies, dispatchError) {
790816

791817
outputs.forEach(outIdProp => {
792818
const {id: outId, property} = outIdProp;
819+
// check if this output is also an input to the same callback
820+
const alsoInput = checkInOutOverlap(outIdProp, inputs);
793821
if (typeof outId === 'object') {
794822
const outIdList = makeAllIds(outId, {});
795823
outIdList.forEach(id => {
796-
addOutputToMulti(id, combineIdAndProp({id, property}));
824+
let tempOutIdProp = {id, property};
825+
let outIdName = combineIdAndProp(tempOutIdProp);
826+
// if this output is also an input, add `outputTag` to the name
827+
if (alsoInput){
828+
duplicateOutputs.push(tempOutIdProp);
829+
outIdName += outputTag;
830+
}
831+
addOutputToMulti(id, outIdName);
797832
});
798-
799833
addPattern(outputPatterns, outId, property, finalDependency);
800834
} else {
801-
addOutputToMulti({}, combineIdAndProp(outIdProp));
835+
let outIdName = combineIdAndProp(outIdProp);
836+
// if this output is also an input, add `outputTag` to the name
837+
if (alsoInput){
838+
duplicateOutputs.push(outIdProp);
839+
outIdName += outputTag;
840+
}
841+
addOutputToMulti({}, outIdName);
802842
addMap(outputMap, outId, property, finalDependency);
803843
}
804844
});
@@ -813,6 +853,25 @@ export function computeGraphs(dependencies, dispatchError) {
813853
});
814854
});
815855

856+
// second pass for adding new output nodes as dependencies where needed
857+
duplicateOutputs.forEach(dupeOutIdProp => {
858+
let originalName = combineIdAndProp(dupeOutIdProp);
859+
let newName = originalName.concat(outputTag);
860+
for (var cnt=0; cnt<cbIn.length; cnt++){
861+
// check if input to the callback
862+
if (cbIn[cnt].some(inName=>(inName==originalName))){
863+
/* make sure it's not also an output of the callback
864+
* (this will be the original callback)
865+
*/
866+
if(!cbOut[cnt].some(outName=>(outName==newName))){
867+
cbOut[cnt].forEach(outName => {
868+
addInputToMulti(newName, outName, false);
869+
});
870+
}
871+
}
872+
}
873+
});
874+
816875
return finalGraphs;
817876
}
818877

0 commit comments

Comments
 (0)