@@ -239,7 +239,6 @@ function validateDependencies(parsedDependencies, dispatchError) {
239
239
} ) ;
240
240
241
241
findDuplicateOutputs ( outputs , head , dispatchError , outStrs , outObjs ) ;
242
- findInOutOverlap ( outputs , inputs , head , dispatchError ) ;
243
242
findMismatchedWildcards ( outputs , inputs , state , head , dispatchError ) ;
244
243
} ) ;
245
244
}
@@ -364,31 +363,21 @@ function findDuplicateOutputs(outputs, head, dispatchError, outStrs, outObjs) {
364
363
} ) ;
365
364
}
366
365
367
- function findInOutOverlap ( outputs , inputs , head , dispatchError ) {
368
- outputs . forEach ( ( out , outi ) => {
369
- const { id : outId , property : outProp } = out ;
370
- inputs . forEach ( ( in_ , ini ) => {
371
- const { id : inId , property : inProp } = in_ ;
372
- if ( outProp !== inProp || typeof outId !== typeof inId ) {
373
- return ;
374
- }
375
- if ( typeof outId === 'string' ) {
376
- if ( outId === inId ) {
377
- dispatchError ( 'Same `Input` and `Output`' , [
378
- head ,
379
- `Input ${ ini } (${ combineIdAndProp ( in_ ) } )` ,
380
- `matches Output ${ outi } (${ combineIdAndProp ( out ) } )`
381
- ] ) ;
382
- }
383
- } else if ( wildcardOverlap ( in_ , [ out ] ) ) {
384
- dispatchError ( 'Same `Input` and `Output`' , [
385
- head ,
386
- `Input ${ ini } (${ combineIdAndProp ( in_ ) } )` ,
387
- 'can match the same component(s) as' ,
388
- `Output ${ outi } (${ combineIdAndProp ( out ) } )`
389
- ] ) ;
366
+ function checkInOutOverlap ( out , inputs ) {
367
+ const { id : outId , property : outProp } = out ;
368
+ return inputs . some ( in_ => {
369
+ const { id : inId , property : inProp } = in_ ;
370
+ if ( outProp !== inProp || typeof outId !== typeof inId ) {
371
+ return false ;
372
+ }
373
+ if ( typeof outId === 'string' ) {
374
+ if ( outId === inId ) {
375
+ return true ;
390
376
}
391
- } ) ;
377
+ } else if ( wildcardOverlap ( in_ , [ out ] ) ) {
378
+ return true ;
379
+ }
380
+ return false ;
392
381
} ) ;
393
382
}
394
383
@@ -749,15 +738,52 @@ export function computeGraphs(dependencies, dispatchError) {
749
738
return idList ;
750
739
}
751
740
741
+ /* multiGraph is used only for testing circularity
742
+ *
743
+ * Each component+property that is used as an input or output is added as a node
744
+ * to a directed graph with a dependency from each input to each output. The
745
+ * function triggerDefaultState in index.js then checks this graph for circularity.
746
+ *
747
+ * In order to allow the same component+property to be both an input and output
748
+ * of the same callback, a two pass approach is used.
749
+ *
750
+ * In the first pass, the graph is built up normally with the exception that
751
+ * in cases where an output is also an input to the same callback a special
752
+ * "output" node is added and the dependencies target this output node instead.
753
+ * For example, if `slider.value` is both an input and an output, then the a new
754
+ * node `slider.value__output` will be added with a dependency from `slider.value`
755
+ * to `slider.value__output`. Splitting the input and output into separate nodes
756
+ * removes the circularity.
757
+ *
758
+ * In order to still detect other forms of circularity, it is necessary to do a
759
+ * second pass and add the new output nodes as a dependency in any *other* callbacks
760
+ * where the original node was an input. Continuing the example, any other callback
761
+ * that had `slider.value` as an input dependency also needs to have
762
+ * `slider.value__output` as a dependency. To make this efficient, all the inputs
763
+ * and outputs for each callback are stored during the first pass.
764
+ */
765
+
766
+ const outputTag = '__output' ;
767
+ const duplicateOutputs = [ ] ;
768
+ const cbIn = [ ] ;
769
+ const cbOut = [ ] ;
770
+
771
+ function addInputToMulti ( inIdProp , outIdProp , firstPass = true ) {
772
+ multiGraph . addNode ( inIdProp ) ;
773
+ multiGraph . addDependency ( inIdProp , outIdProp ) ;
774
+ // only store callback inputs and outputs during the first pass
775
+ if ( firstPass ) {
776
+ cbIn [ cbIn . length - 1 ] . push ( inIdProp ) ;
777
+ cbOut [ cbOut . length - 1 ] . push ( outIdProp ) ;
778
+ }
779
+ }
780
+
752
781
parsedDependencies . forEach ( function registerDependency ( dependency ) {
753
782
const { outputs, inputs} = dependency ;
754
783
755
- // multiGraph - just for testing circularity
756
-
757
- function addInputToMulti ( inIdProp , outIdProp ) {
758
- multiGraph . addNode ( inIdProp ) ;
759
- multiGraph . addDependency ( inIdProp , outIdProp ) ;
760
- }
784
+ // new callback, add an empty array for its inputs and outputs
785
+ cbIn . push ( [ ] ) ;
786
+ cbOut . push ( [ ] ) ;
761
787
762
788
function addOutputToMulti ( outIdFinal , outIdProp ) {
763
789
multiGraph . addNode ( outIdProp ) ;
@@ -791,15 +817,29 @@ export function computeGraphs(dependencies, dispatchError) {
791
817
792
818
outputs . forEach ( outIdProp => {
793
819
const { id : outId , property} = outIdProp ;
820
+ // check if this output is also an input to the same callback
821
+ const alsoInput = checkInOutOverlap ( outIdProp , inputs ) ;
794
822
if ( typeof outId === 'object' ) {
795
823
const outIdList = makeAllIds ( outId , { } ) ;
796
824
outIdList . forEach ( id => {
797
- addOutputToMulti ( id , combineIdAndProp ( { id, property} ) ) ;
825
+ const tempOutIdProp = { id, property} ;
826
+ let outIdName = combineIdAndProp ( tempOutIdProp ) ;
827
+ // if this output is also an input, add `outputTag` to the name
828
+ if ( alsoInput ) {
829
+ duplicateOutputs . push ( tempOutIdProp ) ;
830
+ outIdName += outputTag ;
831
+ }
832
+ addOutputToMulti ( id , outIdName ) ;
798
833
} ) ;
799
-
800
834
addPattern ( outputPatterns , outId , property , finalDependency ) ;
801
835
} else {
802
- addOutputToMulti ( { } , combineIdAndProp ( outIdProp ) ) ;
836
+ let outIdName = combineIdAndProp ( outIdProp ) ;
837
+ // if this output is also an input, add `outputTag` to the name
838
+ if ( alsoInput ) {
839
+ duplicateOutputs . push ( outIdProp ) ;
840
+ outIdName += outputTag ;
841
+ }
842
+ addOutputToMulti ( { } , outIdName ) ;
803
843
addMap ( outputMap , outId , property , finalDependency ) ;
804
844
}
805
845
} ) ;
@@ -814,6 +854,25 @@ export function computeGraphs(dependencies, dispatchError) {
814
854
} ) ;
815
855
} ) ;
816
856
857
+ // second pass for adding new output nodes as dependencies where needed
858
+ duplicateOutputs . forEach ( dupeOutIdProp => {
859
+ const originalName = combineIdAndProp ( dupeOutIdProp ) ;
860
+ const newName = originalName . concat ( outputTag ) ;
861
+ for ( var cnt = 0 ; cnt < cbIn . length ; cnt ++ ) {
862
+ // check if input to the callback
863
+ if ( cbIn [ cnt ] . some ( inName => inName === originalName ) ) {
864
+ /* make sure it's not also an output of the callback
865
+ * (this will be the original callback)
866
+ */
867
+ if ( ! cbOut [ cnt ] . some ( outName => outName === newName ) ) {
868
+ cbOut [ cnt ] . forEach ( outName => {
869
+ addInputToMulti ( newName , outName , false ) ;
870
+ } ) ;
871
+ }
872
+ }
873
+ }
874
+ } ) ;
875
+
817
876
return finalGraphs ;
818
877
}
819
878
0 commit comments