@@ -61,6 +61,51 @@ export interface Call<A, R> extends Write<A>, Read<R> {
61
61
invoke : ( args : A ) => R | undefined ;
62
62
}
63
63
64
+ export enum CanConnectResult {
65
+ SUCCESS = 0 ,
66
+ SELF_LOOP ,
67
+ DESTINATION_OCCUPIED ,
68
+ DOWNSTREAM_WRITE_CONFLICT ,
69
+ NOT_IN_SCOPE ,
70
+ RT_CONNECTION_OUTSIDE_CONTAINER ,
71
+ RT_DIRECT_FEED_THROUGH ,
72
+ RT_CYCLE ,
73
+ MUTATION_CAUSALITY_LOOP
74
+ }
75
+
76
+ const CanConnectResultReasonMap = new Map < CanConnectResult , string > ( [
77
+ [ CanConnectResult . SUCCESS , "Connection successful." ] ,
78
+ [
79
+ CanConnectResult . SELF_LOOP ,
80
+ "Source port and destination port are the same."
81
+ ] ,
82
+ [
83
+ CanConnectResult . DESTINATION_OCCUPIED ,
84
+ "Destination port is already occupied."
85
+ ] ,
86
+ [
87
+ CanConnectResult . DOWNSTREAM_WRITE_CONFLICT ,
88
+ "Destination port is already occupied."
89
+ ] ,
90
+ [
91
+ CanConnectResult . NOT_IN_SCOPE ,
92
+ "Source and destination ports are not in scope."
93
+ ] ,
94
+ [
95
+ CanConnectResult . RT_CONNECTION_OUTSIDE_CONTAINER ,
96
+ "New connection is outside of container."
97
+ ] ,
98
+ [
99
+ CanConnectResult . RT_DIRECT_FEED_THROUGH ,
100
+ "New connection introduces direct feed through."
101
+ ] ,
102
+ [ CanConnectResult . RT_CYCLE , "New connection introduces cycle." ] ,
103
+ [
104
+ CanConnectResult . MUTATION_CAUSALITY_LOOP ,
105
+ "New connection will change the causal effect of the mutation that triggered this connection."
106
+ ]
107
+ ] ) ;
108
+
64
109
/**
65
110
* Abstract class for a schedulable action. It is intended as a wrapper for a
66
111
* regular action. In addition to a get method, it also has a schedule method
@@ -1091,18 +1136,21 @@ export abstract class Reactor extends Component {
1091
1136
* @param src The start point of a new connection.
1092
1137
* @param dst The end point of a new connection.
1093
1138
*/
1094
- public canConnect < R , S extends R > ( src : IOPort < S > , dst : IOPort < R > ) : boolean {
1139
+ public canConnect < R , S extends R > (
1140
+ src : IOPort < S > ,
1141
+ dst : IOPort < R >
1142
+ ) : CanConnectResult {
1095
1143
// Immediate rule out trivial self loops.
1096
1144
if ( src === dst ) {
1097
- throw Error ( "Source port and destination port are the same." ) ;
1145
+ return CanConnectResult . SELF_LOOP ;
1098
1146
}
1099
1147
1100
1148
// Check the race condition
1101
1149
// - between reactors and reactions (NOTE: check also needs to happen
1102
1150
// in addReaction)
1103
1151
const deps = this . _dependencyGraph . getUpstreamNeighbors ( dst ) ; // FIXME this will change with multiplex ports
1104
1152
if ( deps !== undefined && deps . size > 0 ) {
1105
- throw Error ( "Destination port is already occupied." ) ;
1153
+ return CanConnectResult . DESTINATION_OCCUPIED ;
1106
1154
}
1107
1155
1108
1156
if ( ! this . _runtime . isRunning ( ) ) {
@@ -1114,10 +1162,13 @@ export abstract class Reactor extends Component {
1114
1162
// Rule out write conflicts.
1115
1163
// - (between reactors)
1116
1164
if ( this . _dependencyGraph . getDownstreamNeighbors ( dst ) . size > 0 ) {
1117
- return false ;
1165
+ return CanConnectResult . DOWNSTREAM_WRITE_CONFLICT ;
1118
1166
}
1119
1167
1120
- return this . _isInScope ( src , dst ) ;
1168
+ if ( ! this . _isInScope ( src , dst ) ) {
1169
+ return CanConnectResult . NOT_IN_SCOPE ;
1170
+ }
1171
+ return CanConnectResult . SUCCESS ;
1121
1172
} else {
1122
1173
// Attempt to make a connection while executing.
1123
1174
// Check the local dependency graph to figure out whether this change
@@ -1131,7 +1182,7 @@ export abstract class Reactor extends Component {
1131
1182
src . _isContainedBy ( this ) &&
1132
1183
dst . _isContainedBy ( this )
1133
1184
) {
1134
- throw Error ( "New connection is outside of container." ) ;
1185
+ return CanConnectResult . RT_CONNECTION_OUTSIDE_CONTAINER ;
1135
1186
}
1136
1187
1137
1188
// Take the local graph and merge in all the causality interfaces
@@ -1148,23 +1199,21 @@ export abstract class Reactor extends Component {
1148
1199
1149
1200
// 1) check for loops
1150
1201
const hasCycle = graph . hasCycle ( ) ;
1202
+ if ( hasCycle ) {
1203
+ return CanConnectResult . RT_CYCLE ;
1204
+ }
1151
1205
1152
1206
// 2) check for direct feed through.
1153
1207
// 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." ) ;
1208
+ if (
1209
+ src instanceof InPort &&
1210
+ dst instanceof OutPort &&
1211
+ dst . getContainer ( ) === src . getContainer ( )
1212
+ ) {
1213
+ return CanConnectResult . RT_DIRECT_FEED_THROUGH ;
1165
1214
}
1166
1215
1167
- return true ;
1216
+ return CanConnectResult . SUCCESS ;
1168
1217
}
1169
1218
}
1170
1219
@@ -1258,11 +1307,17 @@ export abstract class Reactor extends Component {
1258
1307
if ( dst === undefined || dst === null ) {
1259
1308
throw new Error ( "Cannot connect unspecified destination" ) ;
1260
1309
}
1261
- if ( this . canConnect ( src , dst ) ) {
1262
- this . _uncheckedConnect ( src , dst ) ;
1263
- } else {
1264
- throw new Error ( `ERROR connecting ${ src } to ${ dst } ` ) ;
1310
+ const canConnectResult = this . canConnect ( src , dst ) ;
1311
+ // I know, this looks a bit weird. But
1312
+ if ( canConnectResult !== CanConnectResult . SUCCESS ) {
1313
+ throw new Error (
1314
+ `ERROR connecting ${ src } to ${ dst } . Reason is ${
1315
+ CanConnectResultReasonMap . get ( canConnectResult ) ??
1316
+ CanConnectResult [ canConnectResult ]
1317
+ } `
1318
+ ) ;
1265
1319
}
1320
+ this . _uncheckedConnect ( src , dst ) ;
1266
1321
}
1267
1322
1268
1323
protected _connectMulti < R , S extends R > (
@@ -1316,7 +1371,8 @@ export abstract class Reactor extends Component {
1316
1371
}
1317
1372
1318
1373
for ( let i = 0 ; i < leftPorts . length && i < rightPorts . length ; i ++ ) {
1319
- if ( ! this . canConnect ( leftPorts [ i ] , rightPorts [ i ] ) ) {
1374
+ const canConnectResult = this . canConnect ( leftPorts [ i ] , rightPorts [ i ] ) ;
1375
+ if ( canConnectResult !== CanConnectResult . SUCCESS ) {
1320
1376
throw new Error (
1321
1377
`ERROR connecting ${ leftPorts [ i ] }
1322
1378
to ${ rightPorts [ i ] }
0 commit comments