@@ -61,6 +61,18 @@ export interface Call<A, R> extends Write<A>, Read<R> {
6161 invoke : ( args : A ) => R | undefined ;
6262}
6363
64+ export enum CanConnectResult {
65+ SUCCESS = 0 ,
66+ SELF_LOOP = "Source port and destination port are the same." ,
67+ DESTINATION_OCCUPIED = "Destination port is already occupied." ,
68+ DOWNSTREAM_WRITE_CONFLICT = "Write conflict: port is already occupied." ,
69+ NOT_IN_SCOPE = "Source and destination ports are not in scope." ,
70+ RT_CONNECTION_OUTSIDE_CONTAINER = "New connection is outside of container." ,
71+ RT_DIRECT_FEED_THROUGH = "New connection introduces direct feed through." ,
72+ RT_CYCLE = "New connection introduces cycle." ,
73+ MUTATION_CAUSALITY_LOOP = "New connection will change the causal effect of the mutation that triggered this connection."
74+ }
75+
6476/**
6577 * Abstract class for a schedulable action. It is intended as a wrapper for a
6678 * regular action. In addition to a get method, it also has a schedule method
@@ -1091,18 +1103,21 @@ export abstract class Reactor extends Component {
10911103 * @param src The start point of a new connection.
10921104 * @param dst The end point of a new connection.
10931105 */
1094- public canConnect < R , S extends R > ( src : IOPort < S > , dst : IOPort < R > ) : boolean {
1106+ public canConnect < R , S extends R > (
1107+ src : IOPort < S > ,
1108+ dst : IOPort < R >
1109+ ) : CanConnectResult {
10951110 // Immediate rule out trivial self loops.
10961111 if ( src === dst ) {
1097- throw Error ( "Source port and destination port are the same." ) ;
1112+ return CanConnectResult . SELF_LOOP ;
10981113 }
10991114
11001115 // Check the race condition
11011116 // - between reactors and reactions (NOTE: check also needs to happen
11021117 // in addReaction)
11031118 const deps = this . _dependencyGraph . getUpstreamNeighbors ( dst ) ; // FIXME this will change with multiplex ports
11041119 if ( deps !== undefined && deps . size > 0 ) {
1105- throw Error ( "Destination port is already occupied." ) ;
1120+ return CanConnectResult . DESTINATION_OCCUPIED ;
11061121 }
11071122
11081123 if ( ! this . _runtime . isRunning ( ) ) {
@@ -1114,10 +1129,13 @@ export abstract class Reactor extends Component {
11141129 // Rule out write conflicts.
11151130 // - (between reactors)
11161131 if ( this . _dependencyGraph . getDownstreamNeighbors ( dst ) . size > 0 ) {
1117- return false ;
1132+ return CanConnectResult . DOWNSTREAM_WRITE_CONFLICT ;
11181133 }
11191134
1120- return this . _isInScope ( src , dst ) ;
1135+ if ( ! this . _isInScope ( src , dst ) ) {
1136+ return CanConnectResult . NOT_IN_SCOPE ;
1137+ }
1138+ return CanConnectResult . SUCCESS ;
11211139 } else {
11221140 // Attempt to make a connection while executing.
11231141 // Check the local dependency graph to figure out whether this change
@@ -1131,7 +1149,7 @@ export abstract class Reactor extends Component {
11311149 src . _isContainedBy ( this ) &&
11321150 dst . _isContainedBy ( this )
11331151 ) {
1134- throw Error ( "New connection is outside of container." ) ;
1152+ return CanConnectResult . RT_CONNECTION_OUTSIDE_CONTAINER ;
11351153 }
11361154
11371155 // Take the local graph and merge in all the causality interfaces
@@ -1148,23 +1166,21 @@ export abstract class Reactor extends Component {
11481166
11491167 // 1) check for loops
11501168 const hasCycle = graph . hasCycle ( ) ;
1169+ if ( hasCycle ) {
1170+ return CanConnectResult . RT_CYCLE ;
1171+ }
11511172
11521173 // 2) check for direct feed through.
11531174 // FIXME: This doesn't handle while direct feed thorugh cases.
1154- let hasDirectFeedThrough = false ;
1155- if ( src instanceof InPort && dst instanceof OutPort ) {
1156- hasDirectFeedThrough = dst . getContainer ( ) === src . getContainer ( ) ;
1157- }
1158- // Throw error cases
1159- if ( hasDirectFeedThrough && hasCycle ) {
1160- throw Error ( "New connection introduces direct feed through and cycle." ) ;
1161- } else if ( hasCycle ) {
1162- throw Error ( "New connection introduces cycle." ) ;
1163- } else if ( hasDirectFeedThrough ) {
1164- throw Error ( "New connection introduces direct feed through." ) ;
1175+ if (
1176+ src instanceof InPort &&
1177+ dst instanceof OutPort &&
1178+ dst . getContainer ( ) === src . getContainer ( )
1179+ ) {
1180+ return CanConnectResult . RT_DIRECT_FEED_THROUGH ;
11651181 }
11661182
1167- return true ;
1183+ return CanConnectResult . SUCCESS ;
11681184 }
11691185 }
11701186
@@ -1258,11 +1274,14 @@ export abstract class Reactor extends Component {
12581274 if ( dst === undefined || dst === null ) {
12591275 throw new Error ( "Cannot connect unspecified destination" ) ;
12601276 }
1261- if ( this . canConnect ( src , dst ) ) {
1262- this . _uncheckedConnect ( src , dst ) ;
1263- } else {
1264- throw new Error ( `ERROR connecting ${ src } to ${ dst } ` ) ;
1277+ const canConnectResult = this . canConnect ( src , dst ) ;
1278+ // I know, this looks a bit weird. But
1279+ if ( canConnectResult !== CanConnectResult . SUCCESS ) {
1280+ throw new Error (
1281+ `ERROR connecting ${ src } to ${ dst } . Reason is ${ canConnectResult . valueOf ( ) } `
1282+ ) ;
12651283 }
1284+ this . _uncheckedConnect ( src , dst ) ;
12661285 }
12671286
12681287 protected _connectMulti < R , S extends R > (
@@ -1316,7 +1335,8 @@ export abstract class Reactor extends Component {
13161335 }
13171336
13181337 for ( let i = 0 ; i < leftPorts . length && i < rightPorts . length ; i ++ ) {
1319- if ( ! this . canConnect ( leftPorts [ i ] , rightPorts [ i ] ) ) {
1338+ const canConnectResult = this . canConnect ( leftPorts [ i ] , rightPorts [ i ] ) ;
1339+ if ( canConnectResult !== CanConnectResult . SUCCESS ) {
13201340 throw new Error (
13211341 `ERROR connecting ${ leftPorts [ i ] }
13221342 to ${ rightPorts [ i ] }
0 commit comments