41 lines
2.8 KiB
TypeScript
41 lines
2.8 KiB
TypeScript
import type { ImportLocator } from './imports.ts';
|
|
import { fault } from '../canvas-core/values.ts';
|
|
|
|
/** Ready SCCs are indivisible. Ordinary records retain source-order FIFO selection. */
|
|
export function selectImportGroups(pending: readonly ImportLocator[], committed: ReadonlySet<string>, limits: {units: number; bytes: number; changes: number}): readonly (readonly ImportLocator[])[] {
|
|
const byKey = new Map(pending.map((row, index) => [row.key, {row, index}])), edges = new Map<string, string[]>(), reverse = new Map<string, string[]>();
|
|
for (const row of pending) {
|
|
const dependencies = row.dependencies.filter(key => byKey.has(key)); edges.set(row.key, dependencies);
|
|
for (const key of dependencies) { const list = reverse.get(key) ?? []; list.push(row.key); reverse.set(key, list); }
|
|
}
|
|
const seen = new Set<string>(), finish: string[] = [];
|
|
for (const row of pending) {
|
|
if (seen.has(row.key)) continue;
|
|
seen.add(row.key); const stack = [{key: row.key, index: 0}];
|
|
while (stack.length) {
|
|
const frame = stack.at(-1)!, neighbors = edges.get(frame.key)!;
|
|
if (frame.index === neighbors.length) { finish.push(frame.key); stack.pop(); continue; }
|
|
const key = neighbors[frame.index++]; if (!seen.has(key)) { seen.add(key); stack.push({key, index: 0}); }
|
|
}
|
|
}
|
|
seen.clear(); const components: ImportLocator[][] = [];
|
|
for (let i = finish.length - 1; i >= 0; i--) {
|
|
const first = finish[i]; if (seen.has(first)) continue;
|
|
const stack = [first], group: ImportLocator[] = []; seen.add(first);
|
|
while (stack.length) { const key = stack.pop()!; group.push(byKey.get(key)!.row); for (const next of reverse.get(key) ?? []) if (!seen.has(next)) { seen.add(next); stack.push(next); } }
|
|
group.sort((a, b) => byKey.get(a.key)!.index - byKey.get(b.key)!.index); components.push(group);
|
|
}
|
|
components.sort((a, b) => byKey.get(a[0].key)!.index - byKey.get(b[0].key)!.index);
|
|
const available = new Set(committed), chosen: ImportLocator[][] = []; let units = 0, bytes = 0, changes = 0;
|
|
for (const group of components) {
|
|
const internal = new Set(group.map(row => row.key));
|
|
if (!group.every(row => row.dependencies.every(key => available.has(key) || internal.has(key)))) continue;
|
|
const size = group.reduce((sum, row) => sum + row.byteLength, 0), count = group.reduce((sum, row) => sum + row.changeCount, 0);
|
|
if (size > limits.bytes || count > limits.changes) throw fault('import_dependency_group_capacity');
|
|
// The frame's adaptive unit budget is soft for one SCC, byte/change safety limits are hard.
|
|
if (units && units + group.length > limits.units || bytes + size > limits.bytes || changes + count > limits.changes) continue;
|
|
chosen.push(group); units += group.length; bytes += size; changes += count; for (const row of group) available.add(row.key);
|
|
}
|
|
return chosen;
|
|
}
|