diff --git a/src/core/reactor.ts b/src/core/reactor.ts
index 075e863a..603a568e 100644
--- a/src/core/reactor.ts
+++ b/src/core/reactor.ts
@@ -61,6 +61,18 @@ export interface Call extends Write, Read {
invoke: (args: A) => R | undefined;
}
+export enum CanConnectResult {
+ SUCCESS = 0,
+ SELF_LOOP = "Source port and destination port are the same.",
+ DESTINATION_OCCUPIED = "Destination port is already occupied.",
+ DOWNSTREAM_WRITE_CONFLICT = "Write conflict: port is already occupied.",
+ NOT_IN_SCOPE = "Source and destination ports are not in scope.",
+ RT_CONNECTION_OUTSIDE_CONTAINER = "New connection is outside of container.",
+ RT_DIRECT_FEED_THROUGH = "New connection introduces direct feed through.",
+ RT_CYCLE = "New connection introduces cycle.",
+ MUTATION_CAUSALITY_LOOP = "New connection will change the causal effect of the mutation that triggered this connection."
+}
+
/**
* Abstract class for a schedulable action. It is intended as a wrapper for a
* regular action. In addition to a get method, it also has a schedule method
@@ -1091,10 +1103,13 @@ export abstract class Reactor extends Component {
* @param src The start point of a new connection.
* @param dst The end point of a new connection.
*/
- public canConnect(src: IOPort, dst: IOPort): boolean {
+ public canConnect(
+ src: IOPort,
+ dst: IOPort
+ ): CanConnectResult {
// Immediate rule out trivial self loops.
if (src === dst) {
- throw Error("Source port and destination port are the same.");
+ return CanConnectResult.SELF_LOOP;
}
// Check the race condition
@@ -1102,7 +1117,7 @@ export abstract class Reactor extends Component {
// in addReaction)
const deps = this._dependencyGraph.getUpstreamNeighbors(dst); // FIXME this will change with multiplex ports
if (deps !== undefined && deps.size > 0) {
- throw Error("Destination port is already occupied.");
+ return CanConnectResult.DESTINATION_OCCUPIED;
}
if (!this._runtime.isRunning()) {
@@ -1114,10 +1129,13 @@ export abstract class Reactor extends Component {
// Rule out write conflicts.
// - (between reactors)
if (this._dependencyGraph.getDownstreamNeighbors(dst).size > 0) {
- return false;
+ return CanConnectResult.DOWNSTREAM_WRITE_CONFLICT;
}
- return this._isInScope(src, dst);
+ if (!this._isInScope(src, dst)) {
+ return CanConnectResult.NOT_IN_SCOPE;
+ }
+ return CanConnectResult.SUCCESS;
} else {
// Attempt to make a connection while executing.
// Check the local dependency graph to figure out whether this change
@@ -1131,7 +1149,7 @@ export abstract class Reactor extends Component {
src._isContainedBy(this) &&
dst._isContainedBy(this)
) {
- throw Error("New connection is outside of container.");
+ return CanConnectResult.RT_CONNECTION_OUTSIDE_CONTAINER;
}
// Take the local graph and merge in all the causality interfaces
@@ -1148,23 +1166,21 @@ export abstract class Reactor extends Component {
// 1) check for loops
const hasCycle = graph.hasCycle();
+ if (hasCycle) {
+ return CanConnectResult.RT_CYCLE;
+ }
// 2) check for direct feed through.
// FIXME: This doesn't handle while direct feed thorugh cases.
- let hasDirectFeedThrough = false;
- if (src instanceof InPort && dst instanceof OutPort) {
- hasDirectFeedThrough = dst.getContainer() === src.getContainer();
- }
- // Throw error cases
- if (hasDirectFeedThrough && hasCycle) {
- throw Error("New connection introduces direct feed through and cycle.");
- } else if (hasCycle) {
- throw Error("New connection introduces cycle.");
- } else if (hasDirectFeedThrough) {
- throw Error("New connection introduces direct feed through.");
+ if (
+ src instanceof InPort &&
+ dst instanceof OutPort &&
+ dst.getContainer() === src.getContainer()
+ ) {
+ return CanConnectResult.RT_DIRECT_FEED_THROUGH;
}
- return true;
+ return CanConnectResult.SUCCESS;
}
}
@@ -1258,11 +1274,14 @@ export abstract class Reactor extends Component {
if (dst === undefined || dst === null) {
throw new Error("Cannot connect unspecified destination");
}
- if (this.canConnect(src, dst)) {
- this._uncheckedConnect(src, dst);
- } else {
- throw new Error(`ERROR connecting ${src} to ${dst}`);
+ const canConnectResult = this.canConnect(src, dst);
+ // I know, this looks a bit weird. But
+ if (canConnectResult !== CanConnectResult.SUCCESS) {
+ throw new Error(
+ `ERROR connecting ${src} to ${dst}. Reason is ${canConnectResult.valueOf()}`
+ );
}
+ this._uncheckedConnect(src, dst);
}
protected _connectMulti(
@@ -1316,7 +1335,8 @@ export abstract class Reactor extends Component {
}
for (let i = 0; i < leftPorts.length && i < rightPorts.length; i++) {
- if (!this.canConnect(leftPorts[i], rightPorts[i])) {
+ const canConnectResult = this.canConnect(leftPorts[i], rightPorts[i]);
+ if (canConnectResult !== CanConnectResult.SUCCESS) {
throw new Error(
`ERROR connecting ${leftPorts[i]}
to ${rightPorts[i]}