@@ -363,32 +363,21 @@ function findDuplicateOutputs(outputs, head, dispatchError, outStrs, outObjs) {
363
363
} ) ;
364
364
}
365
365
366
- function findInOutOverlap ( outputs , inputs , head , dispatchError ) {
367
- outputs . forEach ( ( out , outi ) => {
366
+ function checkInOutOverlap ( out , inputs ) {
368
367
const { id : outId , property : outProp } = out ;
369
- inputs . forEach ( ( in_ , ini ) => {
368
+ return inputs . some ( in_ => {
370
369
const { id : inId , property : inProp } = in_ ;
371
370
if ( outProp !== inProp || typeof outId !== typeof inId ) {
372
- return ;
371
+ return false ;
373
372
}
374
373
if ( typeof outId === 'string' ) {
375
374
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 ;
381
376
}
382
377
} 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 ;
389
379
}
390
380
} ) ;
391
- } ) ;
392
381
}
393
382
394
383
function findMismatchedWildcards ( outputs , inputs , state , head , dispatchError ) {
@@ -748,15 +737,52 @@ export function computeGraphs(dependencies, dispatchError) {
748
737
return idList ;
749
738
}
750
739
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
+
751
780
parsedDependencies . forEach ( function registerDependency ( dependency ) {
752
781
const { outputs, inputs} = dependency ;
753
782
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 ( [ ] ) ;
760
786
761
787
function addOutputToMulti ( outIdFinal , outIdProp ) {
762
788
multiGraph . addNode ( outIdProp ) ;
@@ -790,15 +816,29 @@ export function computeGraphs(dependencies, dispatchError) {
790
816
791
817
outputs . forEach ( outIdProp => {
792
818
const { id : outId , property} = outIdProp ;
819
+ // check if this output is also an input to the same callback
820
+ const alsoInput = checkInOutOverlap ( outIdProp , inputs ) ;
793
821
if ( typeof outId === 'object' ) {
794
822
const outIdList = makeAllIds ( outId , { } ) ;
795
823
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 ) ;
797
832
} ) ;
798
-
799
833
addPattern ( outputPatterns , outId , property , finalDependency ) ;
800
834
} 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 ) ;
802
842
addMap ( outputMap , outId , property , finalDependency ) ;
803
843
}
804
844
} ) ;
@@ -813,6 +853,25 @@ export function computeGraphs(dependencies, dispatchError) {
813
853
} ) ;
814
854
} ) ;
815
855
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
+
816
875
return finalGraphs ;
817
876
}
818
877
0 commit comments