uto_manager/lib/domain/services/validation_isolate.dart

155 lines
4.6 KiB
Dart

import 'dart:isolate';
import 'package:uto_manager/domain/entities/personnel_record.dart';
import 'package:uto_manager/domain/entities/position_slot.dart';
import 'package:uto_manager/domain/entities/unit_node.dart';
import 'package:uto_manager/domain/services/mtoe_validation_engine.dart';
// ---------------------------------------------------------------------------
// Message types
// ---------------------------------------------------------------------------
/// Request message sent to the validation isolate.
class ValidationRequest {
const ValidationRequest({
required this.units,
required this.slots,
required this.personnel,
required this.replyPort,
});
final List<UnitNode> units;
final List<PositionSlot> slots;
final List<PersonnelRecord> personnel;
final SendPort replyPort;
}
/// Response message returned from the validation isolate.
class ValidationResponse {
const ValidationResponse({
required this.findings,
this.error,
});
final List<ValidationFinding> findings;
final String? error;
bool get hasError => error != null;
}
// ---------------------------------------------------------------------------
// Isolate entry point
// ---------------------------------------------------------------------------
/// Entry point for the background validation isolate.
///
/// Receives [ValidationRequest] messages on [receivePort], runs the
/// [MtoeValidationEngine], and sends a [ValidationResponse] back via the
/// request's [ValidationRequest.replyPort].
void validationIsolateEntryPoint(SendPort mainSendPort) {
final receivePort = ReceivePort();
// Send our receive port to the main isolate so it can send us requests.
mainSendPort.send(receivePort.sendPort);
const engine = MtoeValidationEngine();
receivePort.listen((dynamic message) {
if (message is ValidationRequest) {
try {
final findings = engine.validate(
units: message.units,
slots: message.slots,
personnel: message.personnel,
);
message.replyPort.send(ValidationResponse(findings: findings));
} catch (e) {
message.replyPort.send(
ValidationResponse(findings: const [], error: e.toString()),
);
}
}
});
}
// ---------------------------------------------------------------------------
// IsolateValidationRunner
// ---------------------------------------------------------------------------
/// Manages a long-lived background [Isolate] for running MTOE validation
/// without blocking the UI thread.
///
/// Usage:
/// ```dart
/// final runner = IsolateValidationRunner();
/// await runner.start();
/// final findings = await runner.validate(units: ..., slots: ..., personnel: ...);
/// await runner.dispose();
/// ```
class IsolateValidationRunner {
Isolate? _isolate;
SendPort? _isolateSendPort;
bool _started = false;
/// Whether the isolate has been started and is ready to accept requests.
bool get isStarted => _started;
/// Starts the background validation isolate.
///
/// Must be called before [validate]. Safe to call multiple times — subsequent
/// calls are no-ops if the isolate is already running.
Future<void> start() async {
if (_started) return;
final receivePort = ReceivePort();
_isolate = await Isolate.spawn(
validationIsolateEntryPoint,
receivePort.sendPort,
debugName: 'MtoeValidationIsolate',
);
// Wait for the isolate to send back its receive port.
_isolateSendPort = await receivePort.first as SendPort;
_started = true;
}
/// Runs MTOE validation in the background isolate.
///
/// Returns a [ValidationResponse] with the findings, or an error response
/// if the isolate encountered an exception.
///
/// Throws [StateError] if [start] has not been called.
Future<ValidationResponse> validate({
required List<UnitNode> units,
required List<PositionSlot> slots,
required List<PersonnelRecord> personnel,
}) async {
if (!_started || _isolateSendPort == null) {
throw StateError(
'IsolateValidationRunner.start() must be called before validate().',
);
}
final replyPort = ReceivePort();
_isolateSendPort!.send(
ValidationRequest(
units: units,
slots: slots,
personnel: personnel,
replyPort: replyPort.sendPort,
),
);
final response = await replyPort.first as ValidationResponse;
replyPort.close();
return response;
}
/// Shuts down the background isolate and releases resources.
Future<void> dispose() async {
_isolate?.kill(priority: Isolate.immediate);
_isolate = null;
_isolateSendPort = null;
_started = false;
}
}