@tscircuit/fanout-solver 0.0.35 → 0.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/lib/boundary-exit.ts +52 -0
- package/lib/build-output.ts +32 -10
- package/lib/fanout-exit-position.ts +78 -0
- package/lib/fanout-solver.ts +267 -38
- package/lib/index.ts +16 -13
- package/lib/prepare-buses.ts +261 -36
- package/lib/route-bus.ts +543 -59
- package/lib/route-single-layer-adaptive-exits.ts +2 -2
- package/lib/route-single-layer-push-shove.ts +2 -1
- package/lib/route-via-minimal-winding.ts +898 -0
- package/lib/types.ts +63 -3
- package/lib/validate-fanout-solution.ts +63 -11
- package/package.json +1 -1
package/lib/prepare-buses.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type {
|
|
|
4
4
|
SimpleRouteConnection,
|
|
5
5
|
SimpleRouteJson,
|
|
6
6
|
} from "@tscircuit/capacity-autorouter"
|
|
7
|
+
import { borderTargetIncludesEdge, getCornerBandSide } from "./boundary-exit"
|
|
8
|
+
import { getFanoutExitPositionConfig } from "./fanout-exit-position"
|
|
7
9
|
import { distance, pointIsInsideObstacle } from "./geometry"
|
|
8
10
|
import type {
|
|
9
11
|
Bounds,
|
|
@@ -12,6 +14,9 @@ import type {
|
|
|
12
14
|
FanoutBusSpec,
|
|
13
15
|
FanoutBusTermination,
|
|
14
16
|
FanoutDirection,
|
|
17
|
+
FanoutEdge,
|
|
18
|
+
FanoutExitPosition,
|
|
19
|
+
FanoutExitPositionConfig,
|
|
15
20
|
FanoutSolverOptions,
|
|
16
21
|
PreparedBus,
|
|
17
22
|
PreparedConnection,
|
|
@@ -20,6 +25,8 @@ import type {
|
|
|
20
25
|
export interface AvailableBoundaryRegion {
|
|
21
26
|
direction: FanoutDirection
|
|
22
27
|
preferredExit: FanoutBorderTarget
|
|
28
|
+
/** Physical shared-boundary edge selected by the first region token. */
|
|
29
|
+
exitEdge: FanoutEdge
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
const FANOUT_BORDER_TARGETS = new Set<FanoutBorderTarget>([
|
|
@@ -33,72 +40,90 @@ const FANOUT_BORDER_TARGETS = new Set<FanoutBorderTarget>([
|
|
|
33
40
|
"bottom-right",
|
|
34
41
|
])
|
|
35
42
|
|
|
43
|
+
const FANOUT_EDGES = new Set<FanoutEdge>(["left", "right", "top", "bottom"])
|
|
44
|
+
|
|
36
45
|
const AVAILABLE_BOUNDARY_REGIONS: Readonly<
|
|
37
46
|
Record<FanoutAvailableCornerAndSideInput, AvailableBoundaryRegion>
|
|
38
47
|
> = {
|
|
39
48
|
top_left: {
|
|
40
49
|
direction: "up",
|
|
41
50
|
preferredExit: "top-left",
|
|
51
|
+
exitEdge: "top",
|
|
42
52
|
},
|
|
43
53
|
top_middle: {
|
|
44
54
|
direction: "up",
|
|
45
55
|
preferredExit: "top",
|
|
56
|
+
exitEdge: "top",
|
|
46
57
|
},
|
|
47
58
|
top_right: {
|
|
48
59
|
direction: "up",
|
|
49
60
|
preferredExit: "top-right",
|
|
61
|
+
exitEdge: "top",
|
|
50
62
|
},
|
|
51
63
|
right_top: {
|
|
52
64
|
direction: "right",
|
|
53
65
|
preferredExit: "top-right",
|
|
66
|
+
exitEdge: "right",
|
|
54
67
|
},
|
|
55
68
|
right_middle: {
|
|
56
69
|
direction: "right",
|
|
57
70
|
preferredExit: "right",
|
|
71
|
+
exitEdge: "right",
|
|
58
72
|
},
|
|
59
73
|
right_bottom: {
|
|
60
74
|
direction: "right",
|
|
61
75
|
preferredExit: "bottom-right",
|
|
76
|
+
exitEdge: "right",
|
|
62
77
|
},
|
|
63
78
|
bottom_right: {
|
|
64
79
|
direction: "down",
|
|
65
80
|
preferredExit: "bottom-right",
|
|
81
|
+
exitEdge: "bottom",
|
|
66
82
|
},
|
|
67
83
|
bottom_middle: {
|
|
68
84
|
direction: "down",
|
|
69
85
|
preferredExit: "bottom",
|
|
86
|
+
exitEdge: "bottom",
|
|
70
87
|
},
|
|
71
88
|
bottom_left: {
|
|
72
89
|
direction: "down",
|
|
73
90
|
preferredExit: "bottom-left",
|
|
91
|
+
exitEdge: "bottom",
|
|
74
92
|
},
|
|
75
93
|
left_bottom: {
|
|
76
94
|
direction: "left",
|
|
77
95
|
preferredExit: "bottom-left",
|
|
96
|
+
exitEdge: "left",
|
|
78
97
|
},
|
|
79
98
|
left_middle: {
|
|
80
99
|
direction: "left",
|
|
81
100
|
preferredExit: "left",
|
|
101
|
+
exitEdge: "left",
|
|
82
102
|
},
|
|
83
103
|
left_top: {
|
|
84
104
|
direction: "left",
|
|
85
105
|
preferredExit: "top-left",
|
|
106
|
+
exitEdge: "left",
|
|
86
107
|
},
|
|
87
108
|
top: {
|
|
88
109
|
direction: "up",
|
|
89
110
|
preferredExit: "top",
|
|
111
|
+
exitEdge: "top",
|
|
90
112
|
},
|
|
91
113
|
right: {
|
|
92
114
|
direction: "right",
|
|
93
115
|
preferredExit: "right",
|
|
116
|
+
exitEdge: "right",
|
|
94
117
|
},
|
|
95
118
|
bottom: {
|
|
96
119
|
direction: "down",
|
|
97
120
|
preferredExit: "bottom",
|
|
121
|
+
exitEdge: "bottom",
|
|
98
122
|
},
|
|
99
123
|
left: {
|
|
100
124
|
direction: "left",
|
|
101
125
|
preferredExit: "left",
|
|
126
|
+
exitEdge: "left",
|
|
102
127
|
},
|
|
103
128
|
}
|
|
104
129
|
|
|
@@ -407,6 +432,134 @@ function resolvePreferredExit(
|
|
|
407
432
|
return value
|
|
408
433
|
}
|
|
409
434
|
|
|
435
|
+
function resolveExitEdge(
|
|
436
|
+
busId: string,
|
|
437
|
+
value: FanoutEdge | undefined,
|
|
438
|
+
): FanoutEdge | undefined {
|
|
439
|
+
if (value === undefined) return undefined
|
|
440
|
+
if (!FANOUT_EDGES.has(value)) {
|
|
441
|
+
throw new Error(
|
|
442
|
+
`FanoutSolver: bus "${busId}" has invalid exitEdge "${value}"`,
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
return value
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function resolveExitPosition(
|
|
449
|
+
busId: string,
|
|
450
|
+
value: FanoutExitPosition | undefined,
|
|
451
|
+
): Readonly<FanoutExitPositionConfig> | undefined {
|
|
452
|
+
if (value === undefined) return undefined
|
|
453
|
+
try {
|
|
454
|
+
return getFanoutExitPositionConfig(value)
|
|
455
|
+
} catch {
|
|
456
|
+
throw new Error(
|
|
457
|
+
`FanoutSolver: bus "${busId}" has invalid exitPosition "${value}"`,
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function assertExitPositionFieldMatches<T extends string>(params: {
|
|
463
|
+
busId: string
|
|
464
|
+
exitPosition: FanoutExitPosition
|
|
465
|
+
fieldName: "direction" | "preferredExit" | "exitEdge"
|
|
466
|
+
expected: T | undefined
|
|
467
|
+
actual: T | undefined
|
|
468
|
+
sourceName: string
|
|
469
|
+
}): void {
|
|
470
|
+
const { busId, exitPosition, fieldName, expected, actual, sourceName } =
|
|
471
|
+
params
|
|
472
|
+
if (actual === undefined || actual === expected) return
|
|
473
|
+
throw new Error(
|
|
474
|
+
`FanoutSolver: bus "${busId}" exitPosition "${exitPosition}" conflicts with ${sourceName} ${fieldName} "${actual}"`,
|
|
475
|
+
)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function resolveBusExitFields(params: {
|
|
479
|
+
busId: string
|
|
480
|
+
requestedBus: FanoutBusSpec
|
|
481
|
+
options: FanoutSolverOptions
|
|
482
|
+
}): Pick<
|
|
483
|
+
FanoutBusSpec,
|
|
484
|
+
"exitPosition" | "direction" | "preferredExit" | "exitEdge"
|
|
485
|
+
> {
|
|
486
|
+
const { busId, requestedBus, options } = params
|
|
487
|
+
const exitPosition = requestedBus.exitPosition
|
|
488
|
+
const exitPositionConfig = resolveExitPosition(busId, exitPosition)
|
|
489
|
+
const busPreferredExit = resolvePreferredExit(
|
|
490
|
+
busId,
|
|
491
|
+
requestedBus.preferredExit,
|
|
492
|
+
)
|
|
493
|
+
const optionPreferredExit = resolvePreferredExit(
|
|
494
|
+
busId,
|
|
495
|
+
options.busExitPreferences?.[busId],
|
|
496
|
+
)
|
|
497
|
+
const busExitEdge = resolveExitEdge(busId, requestedBus.exitEdge)
|
|
498
|
+
|
|
499
|
+
if (exitPositionConfig && exitPosition) {
|
|
500
|
+
for (const [actual, sourceName] of [
|
|
501
|
+
[requestedBus.direction, "bus"],
|
|
502
|
+
[options.busDirections?.[busId], "busDirections"],
|
|
503
|
+
] as const) {
|
|
504
|
+
assertExitPositionFieldMatches({
|
|
505
|
+
busId,
|
|
506
|
+
exitPosition,
|
|
507
|
+
fieldName: "direction",
|
|
508
|
+
expected: exitPositionConfig.direction,
|
|
509
|
+
actual,
|
|
510
|
+
sourceName,
|
|
511
|
+
})
|
|
512
|
+
}
|
|
513
|
+
for (const [actual, sourceName] of [
|
|
514
|
+
[busPreferredExit, "bus"],
|
|
515
|
+
[optionPreferredExit, "busExitPreferences"],
|
|
516
|
+
] as const) {
|
|
517
|
+
assertExitPositionFieldMatches({
|
|
518
|
+
busId,
|
|
519
|
+
exitPosition,
|
|
520
|
+
fieldName: "preferredExit",
|
|
521
|
+
expected: exitPositionConfig.preferredExit,
|
|
522
|
+
actual,
|
|
523
|
+
sourceName,
|
|
524
|
+
})
|
|
525
|
+
}
|
|
526
|
+
assertExitPositionFieldMatches({
|
|
527
|
+
busId,
|
|
528
|
+
exitPosition,
|
|
529
|
+
fieldName: "exitEdge",
|
|
530
|
+
expected: exitPositionConfig.exitEdge,
|
|
531
|
+
actual: busExitEdge,
|
|
532
|
+
sourceName: "bus",
|
|
533
|
+
})
|
|
534
|
+
return {
|
|
535
|
+
exitPosition,
|
|
536
|
+
...(exitPositionConfig.direction
|
|
537
|
+
? { direction: exitPositionConfig.direction }
|
|
538
|
+
: {}),
|
|
539
|
+
...(exitPositionConfig.preferredExit
|
|
540
|
+
? { preferredExit: exitPositionConfig.preferredExit }
|
|
541
|
+
: {}),
|
|
542
|
+
...(exitPositionConfig.exitEdge
|
|
543
|
+
? { exitEdge: exitPositionConfig.exitEdge }
|
|
544
|
+
: {}),
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const direction =
|
|
549
|
+
options.busDirections?.[busId] ??
|
|
550
|
+
requestedBus.direction ??
|
|
551
|
+
options.defaultDirection
|
|
552
|
+
const preferredExit = resolvePreferredExit(
|
|
553
|
+
busId,
|
|
554
|
+
optionPreferredExit ?? busPreferredExit ?? options.defaultPreferredExit,
|
|
555
|
+
)
|
|
556
|
+
return {
|
|
557
|
+
...(direction ? { direction } : {}),
|
|
558
|
+
...(preferredExit ? { preferredExit } : {}),
|
|
559
|
+
...(busExitEdge ? { exitEdge: busExitEdge } : {}),
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
410
563
|
function resolveAllowedLayers(
|
|
411
564
|
busId: string,
|
|
412
565
|
allowedLayers: readonly string[] | undefined,
|
|
@@ -446,7 +599,7 @@ export function resolveAvailableBoundaryRegions(
|
|
|
446
599
|
`FanoutSolver: invalid availableCornersAndSides value "${input}"`,
|
|
447
600
|
)
|
|
448
601
|
}
|
|
449
|
-
const key = `${region.direction}:${region.preferredExit}`
|
|
602
|
+
const key = `${region.exitEdge}:${region.direction}:${region.preferredExit}`
|
|
450
603
|
if (seen.has(key)) continue
|
|
451
604
|
seen.add(key)
|
|
452
605
|
regions.push(region)
|
|
@@ -505,17 +658,19 @@ function resolveBusSpecs(
|
|
|
505
658
|
requestedBus.busId,
|
|
506
659
|
(requestedBus as FanoutBusSpec).termination,
|
|
507
660
|
)
|
|
508
|
-
const
|
|
509
|
-
requestedBus.busId,
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
)
|
|
661
|
+
const resolvedExitFields = resolveBusExitFields({
|
|
662
|
+
busId: requestedBus.busId,
|
|
663
|
+
requestedBus: requestedBus as FanoutBusSpec,
|
|
664
|
+
options,
|
|
665
|
+
})
|
|
514
666
|
const allowedLayers = resolveAllowedLayers(
|
|
515
667
|
requestedBus.busId,
|
|
516
668
|
(requestedBus as FanoutBusSpec).allowedLayers,
|
|
517
669
|
)
|
|
518
|
-
if (
|
|
670
|
+
if (
|
|
671
|
+
termination.type === "plane" &&
|
|
672
|
+
resolvedExitFields.preferredExit !== undefined
|
|
673
|
+
) {
|
|
519
674
|
throw new Error(
|
|
520
675
|
`FanoutSolver: plane-terminated bus "${requestedBus.busId}" cannot also specify preferredExit`,
|
|
521
676
|
)
|
|
@@ -525,11 +680,7 @@ function resolveBusSpecs(
|
|
|
525
680
|
sourceComponentId:
|
|
526
681
|
(requestedBus as FanoutBusSpec).sourceComponentId ??
|
|
527
682
|
options.sourceComponentId,
|
|
528
|
-
|
|
529
|
-
options.busDirections?.[requestedBus.busId] ??
|
|
530
|
-
(requestedBus as FanoutBusSpec).direction ??
|
|
531
|
-
options.defaultDirection,
|
|
532
|
-
preferredExit,
|
|
683
|
+
...resolvedExitFields,
|
|
533
684
|
...(allowedLayers === undefined ? {} : { allowedLayers }),
|
|
534
685
|
termination,
|
|
535
686
|
})
|
|
@@ -540,26 +691,26 @@ function resolveBusSpecs(
|
|
|
540
691
|
const inferredBusId = inferBusId(connection)
|
|
541
692
|
if (inferredBusId) {
|
|
542
693
|
const existing = specsById.get(inferredBusId)
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
...
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
}
|
|
694
|
+
if (existing) {
|
|
695
|
+
specsById.set(inferredBusId, {
|
|
696
|
+
...existing,
|
|
697
|
+
connectionNames: [...existing.connectionNames, connection.name],
|
|
698
|
+
})
|
|
699
|
+
} else {
|
|
700
|
+
specsById.set(inferredBusId, {
|
|
701
|
+
busId: inferredBusId,
|
|
702
|
+
connectionNames: [connection.name],
|
|
703
|
+
direction:
|
|
704
|
+
options.busDirections?.[inferredBusId] ?? options.defaultDirection,
|
|
705
|
+
sourceComponentId: options.sourceComponentId,
|
|
706
|
+
preferredExit: resolvePreferredExit(
|
|
707
|
+
inferredBusId,
|
|
708
|
+
options.busExitPreferences?.[inferredBusId] ??
|
|
709
|
+
options.defaultPreferredExit,
|
|
710
|
+
),
|
|
711
|
+
termination: { type: "boundary" },
|
|
712
|
+
})
|
|
713
|
+
}
|
|
563
714
|
} else {
|
|
564
715
|
const singletonBusId = `connection:${connection.name}`
|
|
565
716
|
specsById.set(singletonBusId, {
|
|
@@ -668,7 +819,7 @@ function prepareConnection(params: {
|
|
|
668
819
|
sourceGrid: ComponentGrid
|
|
669
820
|
componentGrids: ComponentGrid[]
|
|
670
821
|
termination: FanoutBusTermination
|
|
671
|
-
exitTargetPoint?: { x: number; y: number }
|
|
822
|
+
exitTargetPoint?: { x: number; y: number; layer?: string }
|
|
672
823
|
}): PreparedConnection {
|
|
673
824
|
const {
|
|
674
825
|
connection,
|
|
@@ -712,7 +863,11 @@ function prepareConnection(params: {
|
|
|
712
863
|
sourceLayer,
|
|
713
864
|
sourceObstacle: sourceMatch.obstacle,
|
|
714
865
|
targetPoint,
|
|
715
|
-
exitTargetPoint: exitTargetPoint ??
|
|
866
|
+
exitTargetPoint: exitTargetPoint ?? {
|
|
867
|
+
x: targetPoint.x,
|
|
868
|
+
y: targetPoint.y,
|
|
869
|
+
},
|
|
870
|
+
hasExplicitLayeredExitTarget: exitTargetPoint?.layer !== undefined,
|
|
716
871
|
}
|
|
717
872
|
}
|
|
718
873
|
throw new Error(
|
|
@@ -892,6 +1047,27 @@ function resolveAvailableBusExit(params: {
|
|
|
892
1047
|
)[0]!
|
|
893
1048
|
}
|
|
894
1049
|
|
|
1050
|
+
function validateExplicitExitAvailability(params: {
|
|
1051
|
+
busId: string
|
|
1052
|
+
exitEdge: FanoutEdge
|
|
1053
|
+
preferredExit: FanoutBorderTarget
|
|
1054
|
+
availableRegions: readonly AvailableBoundaryRegion[]
|
|
1055
|
+
}): void {
|
|
1056
|
+
const { busId, exitEdge, preferredExit, availableRegions } = params
|
|
1057
|
+
const requestedBandSide = getCornerBandSide(exitEdge, preferredExit)
|
|
1058
|
+
const hasCompatibleRegion = availableRegions.some(
|
|
1059
|
+
(region) =>
|
|
1060
|
+
region.exitEdge === exitEdge &&
|
|
1061
|
+
getCornerBandSide(region.exitEdge, region.preferredExit) ===
|
|
1062
|
+
requestedBandSide,
|
|
1063
|
+
)
|
|
1064
|
+
if (!hasCompatibleRegion) {
|
|
1065
|
+
throw new Error(
|
|
1066
|
+
`FanoutSolver: bus "${busId}" cannot use its requested exit with availableCornersAndSides`,
|
|
1067
|
+
)
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
895
1071
|
function resolveBusDirection(params: {
|
|
896
1072
|
busId: string
|
|
897
1073
|
explicitDirection?: FanoutDirection
|
|
@@ -1031,6 +1207,20 @@ export function prepareFanoutBuses(
|
|
|
1031
1207
|
sourceGrid,
|
|
1032
1208
|
preparedConnections,
|
|
1033
1209
|
} of resolvedBusInputs) {
|
|
1210
|
+
if (busSpec.exitEdge && !busSpec.preferredExit) {
|
|
1211
|
+
throw new Error(
|
|
1212
|
+
`FanoutSolver: bus "${busSpec.busId}" exitEdge requires preferredExit`,
|
|
1213
|
+
)
|
|
1214
|
+
}
|
|
1215
|
+
if (
|
|
1216
|
+
busSpec.exitEdge &&
|
|
1217
|
+
busSpec.preferredExit &&
|
|
1218
|
+
!borderTargetIncludesEdge(busSpec.preferredExit, busSpec.exitEdge)
|
|
1219
|
+
) {
|
|
1220
|
+
throw new Error(
|
|
1221
|
+
`FanoutSolver: bus "${busSpec.busId}" exitEdge "${busSpec.exitEdge}" is incompatible with preferredExit "${busSpec.preferredExit}"`,
|
|
1222
|
+
)
|
|
1223
|
+
}
|
|
1034
1224
|
const resolvedExit = resolveBusDirection({
|
|
1035
1225
|
busId: busSpec.busId,
|
|
1036
1226
|
explicitDirection:
|
|
@@ -1039,12 +1229,29 @@ export function prepareFanoutBuses(
|
|
|
1039
1229
|
connections: preparedConnections,
|
|
1040
1230
|
sharedBoundary,
|
|
1041
1231
|
availableRegions:
|
|
1042
|
-
busSpec.termination?.type === "plane"
|
|
1232
|
+
busSpec.termination?.type === "plane" || busSpec.exitEdge
|
|
1233
|
+
? undefined
|
|
1234
|
+
: availableRegions,
|
|
1043
1235
|
})
|
|
1236
|
+
if (
|
|
1237
|
+
busSpec.termination?.type !== "plane" &&
|
|
1238
|
+
busSpec.exitEdge &&
|
|
1239
|
+
resolvedExit.preferredExit &&
|
|
1240
|
+
availableRegions
|
|
1241
|
+
) {
|
|
1242
|
+
validateExplicitExitAvailability({
|
|
1243
|
+
busId: busSpec.busId,
|
|
1244
|
+
exitEdge: busSpec.exitEdge,
|
|
1245
|
+
preferredExit: resolvedExit.preferredExit,
|
|
1246
|
+
availableRegions,
|
|
1247
|
+
})
|
|
1248
|
+
}
|
|
1044
1249
|
buses.push({
|
|
1045
1250
|
busId: busSpec.busId,
|
|
1046
1251
|
direction: resolvedExit.direction,
|
|
1047
1252
|
preferredExit: resolvedExit.preferredExit,
|
|
1253
|
+
...(busSpec.exitEdge ? { exitEdge: busSpec.exitEdge } : {}),
|
|
1254
|
+
cornerBandConnectionCount: 0,
|
|
1048
1255
|
allowedLayers: busSpec.allowedLayers,
|
|
1049
1256
|
termination: busSpec.termination ?? { type: "boundary" },
|
|
1050
1257
|
connections: preparedConnections,
|
|
@@ -1059,5 +1266,23 @@ export function prepareFanoutBuses(
|
|
|
1059
1266
|
})
|
|
1060
1267
|
}
|
|
1061
1268
|
|
|
1269
|
+
const cornerBandConnectionCounts = new Map<string, number>()
|
|
1270
|
+
for (const bus of buses) {
|
|
1271
|
+
const side = getCornerBandSide(bus.exitEdge, bus.preferredExit)
|
|
1272
|
+
if (!bus.exitEdge || !side) continue
|
|
1273
|
+
const key = `${bus.exitEdge}:${side}`
|
|
1274
|
+
cornerBandConnectionCounts.set(
|
|
1275
|
+
key,
|
|
1276
|
+
(cornerBandConnectionCounts.get(key) ?? 0) + bus.connections.length,
|
|
1277
|
+
)
|
|
1278
|
+
}
|
|
1279
|
+
for (const bus of buses) {
|
|
1280
|
+
const side = getCornerBandSide(bus.exitEdge, bus.preferredExit)
|
|
1281
|
+
if (!bus.exitEdge || !side) continue
|
|
1282
|
+
bus.cornerBandConnectionCount =
|
|
1283
|
+
cornerBandConnectionCounts.get(`${bus.exitEdge}:${side}`) ??
|
|
1284
|
+
bus.connections.length
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1062
1287
|
return buses
|
|
1063
1288
|
}
|