computeLayout method
- String jsonData, {
- bool expandAll = false,
- SchematicExpansionMode? expansionMode,
Compute layout from JSON data.
Parses netlist JSON in Dart, then uses ELK for layout computation.
When expandAll is true, all nodes are recursively expanded before
the first layout pass, avoiding a visible collapse-then-expand redraw.
Prefer using expansionMode instead of expandAll. When
expansionMode is provided, expandAll is ignored.
Implementation
Future<void> computeLayout(
String jsonData, {
bool expandAll = false,
SchematicExpansionMode? expansionMode,
}) async {
final engine = _layoutEngine;
if (engine == null) {
if (!mounted) {
return;
}
setState(() {
error = 'Layout engine not available';
isLoading = false;
});
return;
}
try {
if (kPerfLog) {
debugPrint('[PERF] computeLayout ENTERED');
}
schematicJson = jsonData;
SchematicLayoutResult layoutResult;
// Yield a frame so the loading indicator can render before
// the heavy synchronous parse + serialization work begins.
if (mounted) {
final completer = Completer<void>();
WidgetsBinding.instance.addPostFrameCallback((_) {
completer.complete();
});
await completer.future;
if (!mounted) {
return;
}
}
// Parse in Dart, serialize to JS format for ELK
if (kPerfLog) {
debugPrint('[PERF] JSON input size: ${jsonData.length} chars');
}
final parseStart = DateTime.now();
// Parse netlist JSON with the unified adapter
schematicAdapter = NetlistSchematicAdapter.fromJson(jsonData);
final parseEnd = DateTime.now();
if (kPerfLog) {
debugPrint(
'[PERF] NetlistSchematicAdapter.fromJson: '
'${parseEnd.difference(parseStart).inMilliseconds} ms',
);
}
// Resolve the effective expansion mode.
// If expansionMode is provided, use it; otherwise fall back to the
// deprecated expandAll boolean for backward compatibility.
final effectiveMode = expansionMode ??
(expandAll
? SchematicExpansionMode.fullyExpanded
: SchematicExpansionMode.defaultView);
// Apply the requested expansion to the in-memory graph before the
// first ELK layout pass.
_applyExpansionMode(effectiveMode);
// Serialize to JS format and compute layout with timeout.
// toJsGraph() handles all expansion states (collapsed, partial, full).
final jsStart = DateTime.now();
final elkGraph = schematicAdapter!.schematic.toJsGraph();
final jsEnd = DateTime.now();
if (kPerfLog) {
debugPrint(
'[PERF] toJsGraph: ${jsEnd.difference(jsStart).inMilliseconds} ms, '
'elkGraph size: ${elkGraph.length} chars',
);
}
// Wrap the native call with timeout.
// QuickJS (used on Linux native) is significantly slower than
// browser JS engines, so allow generous time for ELK layout.
final elkStart = DateTime.now();
if (kPerfLog) {
debugPrint('[PERF] Starting ELK layout...');
}
layoutResult = await engine
.computeLayoutFromElkGraph(elkGraph, sessionId: _sessionId)
.timeout(const Duration(seconds: 60));
final elkEnd = DateTime.now();
if (kPerfLog) {
debugPrint(
'[PERF] ELK layout: ${elkEnd.difference(elkStart).inMilliseconds} ms',
);
}
if (!mounted) {
return;
}
if (layoutResult.hasError) {
setState(() {
error = 'Layout error: ${layoutResult.error}';
isLoading = false;
});
return;
}
setState(() {
layout = layoutResult;
error = null;
isLoading = false;
});
} on TimeoutException catch (e) {
if (!mounted) {
return;
}
setState(() {
error = 'Schematic layout timed out (module too complex): $e';
isLoading = false;
});
} on Exception catch (e) {
if (!mounted) {
return;
}
setState(() {
error = 'Error computing layout: $e';
isLoading = false;
});
}
}