connectPorts function
- PortReference driver,
- PortReference receiver, {
- String? driverPathNewPortName,
- String? receiverPathNewPortName,
- bool allowDriverPathUniquification = true,
- bool allowReceiverPathUniquification = true,
Makes a connection from driver
to receiver
, punching ports along the way
as necessary.
If driverPathNewPortName
or receiverPathNewPortName
are provided, then
it will prefer to name new ports with those names. If
allowDriverPathUniquification
or allowReceiverPathUniquification
are
false
, then the port names will not be uniquified on those paths.
Uniquification also respects BridgeModule.allowUniquification at each
level.
Implementation
void connectPorts(
PortReference driver,
PortReference receiver, {
String? driverPathNewPortName,
String? receiverPathNewPortName,
bool allowDriverPathUniquification = true,
bool allowReceiverPathUniquification = true,
}) {
// TODO(mkorbel1): need to add better control over naming of intermediate
// ports -- default allow renaming? or should it prefer the top/leaf?
if (driver.module.hasBuilt || receiver.module.hasBuilt) {
throw RohdBridgeException('Cannot connect ports after build.');
}
final driverInstance = driver.module;
final receiverInstance = receiver.module;
BridgeModule? commonParent;
final driverContainsReceiver =
driver.module.getHierarchyDownTo(receiver.module) != null;
final receiverContainsDriver =
receiver.module.getHierarchyDownTo(driver.module) != null;
if (!driverContainsReceiver &&
!receiverContainsDriver &&
driverInstance != receiverInstance &&
(driver.direction == receiver.direction) &&
((driver.direction != PortDirection.inOut) ||
(receiver.direction != PortDirection.inOut))) {
// e.g. feed-through
throw RohdBridgeException(
'Unhandled directionality and hierarchy of driver and receiver.');
} else if ((driverContainsReceiver || receiverContainsDriver) &&
(receiver.direction != driver.direction) &&
(receiver.direction != PortDirection.inOut &&
driver.direction != PortDirection.inOut)) {
final containsStr = driverContainsReceiver
? 'driver ${driver.module.name} contains'
' receiver ${receiver.module.name}'
: 'receiver ${receiver.module.name} contains'
' driver ${driver.module.name}';
throw RohdBridgeException(
'Vertical connections should have the same direction,'
' but with $driver driving $receiver, '
' $containsStr, but directions are'
' ${driver.direction} and ${receiver.direction}, respectively.');
} else {
commonParent =
findCommonParent(driverInstance, receiverInstance) as BridgeModule?;
if (driverContainsReceiver || receiverContainsDriver) {
if (receiver.portName == driver.portName) {
// if we're going up/down and the port names are the same, then we
// should keep the intermediate name the same
driverPathNewPortName ??= driver.portName;
receiverPathNewPortName ??= receiver.portName;
}
}
}
if (commonParent == null) {
throw RohdBridgeException('No common parent found between'
' $driverInstance and $receiverInstance');
}
// start from the driver
var driverPortRef = driver;
if (driverInstance != commonParent) {
// we need to punch upwards from the driver to the common parent
final driverPath = commonParent.getHierarchyDownTo(driverInstance)!;
for (var i = driverPath.length - 2; i >= 1; i--) {
final driverPathI = driverPath[i] as BridgeModule;
final uniqName = driverPathI._getUniquePortName(
driverPortRef,
initialName: driverPathNewPortName,
allowNameUniquification: allowDriverPathUniquification,
);
driverPortRef = driverPortRef.punchUpTo(
driverPathI,
newPortName: uniqName,
);
}
}
// now start from the receiver, pulling up
var receiverPortRef = receiver;
// keep track of all created receiver ports so far so we can update
// the corresponding modules' [_upperSourceMap]s
final createdReceiverPorts = <PortReference>[];
if (receiverInstance != commonParent) {
// we need to punch upwards from the receiver to the common parent
final receiverPath = commonParent.getHierarchyDownTo(receiverInstance)!;
for (var i = receiverPath.length - 2; i >= 1; i--) {
// find if there are ports that are already connected to the driver from
// anywhere up the chain
final upperTargets = TraverseableCollection<PortReference>()
..add(driverPortRef);
// TODO(mkorbel1): is there a more efficient way to do this search? can
// something be cached efficiently?
final receiverPathI = receiverPath[i] as BridgeModule;
for (var upperTargIdx = 0;
upperTargIdx < upperTargets.length;
upperTargIdx++) {
final upperTarg = upperTargets[upperTargIdx];
final upperTargTargs = receiverPath
.getRange(1, i + 1)
.map((receiverPathMod) =>
(receiverPathMod as BridgeModule)._upperSourceMap[upperTarg])
.nonNulls;
for (final iterUpperTargi in upperTargTargs) {
if (receiverPathI._upperSourceMap.containsKey(iterUpperTargi)) {
// if we already have a known connection up to the driver from
// here, then we can just connect to the existing port and exit
// immediately
receiverPortRef
.gets(receiverPathI._upperSourceMap[iterUpperTargi]!);
return;
}
upperTargets.add(iterUpperTargi);
}
}
final uniqName = receiverPathI._getUniquePortName(
receiverPortRef,
initialName: receiverPathNewPortName,
allowNameUniquification: allowReceiverPathUniquification,
);
receiverPortRef =
receiverPortRef.punchUpTo(receiverPathI, newPortName: uniqName);
createdReceiverPorts.add(receiverPortRef);
// now we tell all prior-created ports that they can access the current
// receiver port via the port that was created.
for (final createdReceiverPort in createdReceiverPorts) {
final receiverPortModule = createdReceiverPort.module;
assert(!receiverPortModule._upperSourceMap.containsKey(receiverPortRef),
'should not be recreating a path if one already exists.');
receiverPortModule._upperSourceMap[receiverPortRef] =
createdReceiverPort;
}
}
}
// also notify about the top-level driver
for (final createdReceiverPort in [receiver, ...createdReceiverPorts]) {
final receiverPortModule = createdReceiverPort.module;
receiverPortModule._upperSourceMap[driverPortRef] = createdReceiverPort;
}
receiverPortRef.gets(driverPortRef);
}