Files
Endoleap/src/lib/canvas-runtime/import-groups.ts
T

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;
}