mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-07-18 06:40:52 +10:00
wip
This commit is contained in:
committed by
just-stuff-tm
parent
e6c9a3fea7
commit
3c0c0d1dea
@@ -34,14 +34,6 @@ class UsbSerialService {
|
|||||||
FlSerial? _serial;
|
FlSerial? _serial;
|
||||||
AppDebugLogService? _debugLogService;
|
AppDebugLogService? _debugLogService;
|
||||||
|
|
||||||
/// Holds the last-opened native serial port across hot-restart boundaries.
|
|
||||||
/// On hot restart the Dart isolate is torn down without running [disconnect],
|
|
||||||
/// leaving the native SerialThread alive. The next [connect] call reads this
|
|
||||||
/// field and force-closes the orphaned port before creating a new one, which
|
|
||||||
/// causes the old native thread to unblock its blocking read and exit
|
|
||||||
/// naturally—before any new Dart FFI callbacks are registered.
|
|
||||||
static FlSerial? _lastSerial;
|
|
||||||
|
|
||||||
UsbSerialStatus get status => _status;
|
UsbSerialStatus get status => _status;
|
||||||
String? get activePortKey => _connectedPortKey;
|
String? get activePortKey => _connectedPortKey;
|
||||||
String? get activePortDisplayLabel =>
|
String? get activePortDisplayLabel =>
|
||||||
@@ -151,25 +143,23 @@ class UsbSerialService {
|
|||||||
throw StateError(msg);
|
throw StateError(msg);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Force-close any native serial port left open by the previous Dart
|
// ── Hot-restart guard ─────────────────────────────────────────────────
|
||||||
// isolate (hot-restart case). The old SerialThread blocks on read(); once
|
// On hot restart Dart tears down the isolate without calling dispose().
|
||||||
// the port is closed here it unblocks and exits before we register any
|
// The NativeCallable registered by flserial's setCallback() is
|
||||||
// new Dart FFI callbacks, preventing the "callback invoked after deletion"
|
// isolate-local and gets freed when the isolate dies, but the native
|
||||||
// crash.
|
// SerialThread is still alive and will call it → crash.
|
||||||
final orphan = _lastSerial;
|
//
|
||||||
if (orphan != null) {
|
// flserial uses process-global native state. Calling fl_free() kills ALL
|
||||||
_lastSerial = null;
|
// SerialThreads for every open port across all Dart isolates (there is
|
||||||
try {
|
// only one in a Flutter app). Then fl_init() re-initialises the slot
|
||||||
if (orphan.isOpen() == FlOpenStatus.open) {
|
// table so subsequent fl_open() calls work normally.
|
||||||
orphan.closePort();
|
//
|
||||||
}
|
// This must happen before we register any new NativeCallable, so it must
|
||||||
} catch (_) {}
|
// be the very first thing we do in the desktop branch.
|
||||||
try {
|
try {
|
||||||
orphan.free();
|
bindings.fl_free();
|
||||||
} catch (_) {}
|
bindings.fl_init(16);
|
||||||
// Give the native thread a moment to observe the port closure and exit.
|
} catch (_) {}
|
||||||
await Future<void>.delayed(const Duration(milliseconds: 100));
|
|
||||||
}
|
|
||||||
|
|
||||||
// On macOS, flserial lists both cu.* and tty.* device nodes.
|
// On macOS, flserial lists both cu.* and tty.* device nodes.
|
||||||
// When a cu.* open fails with FL_ERROR_PORT_NOT_EXIST, try the tty.*
|
// When a cu.* open fails with FL_ERROR_PORT_NOT_EXIST, try the tty.*
|
||||||
@@ -203,7 +193,6 @@ class UsbSerialService {
|
|||||||
serial.setRTS(false);
|
serial.setRTS(false);
|
||||||
serial.setDTR(true);
|
serial.setDTR(true);
|
||||||
_serial = serial;
|
_serial = serial;
|
||||||
_lastSerial = serial;
|
|
||||||
// Update the normalized port name to whichever candidate succeeded.
|
// Update the normalized port name to whichever candidate succeeded.
|
||||||
normalizedPortName = candidate;
|
normalizedPortName = candidate;
|
||||||
_debugLogService?.info(
|
_debugLogService?.info(
|
||||||
@@ -213,9 +202,8 @@ class UsbSerialService {
|
|||||||
opened = true;
|
opened = true;
|
||||||
break;
|
break;
|
||||||
} on FlSerialException catch (error) {
|
} on FlSerialException catch (error) {
|
||||||
// Do NOT call fl_free() here — it destroys global native library
|
// The native fl_open() already called fl_close() on failure
|
||||||
// state and makes subsequent fl_init() calls unreliable. The native
|
// internally, so no extra cleanup is needed here for this candidate.
|
||||||
// fl_open() already called fl_close() on failure internally.
|
|
||||||
debugPrint(
|
debugPrint(
|
||||||
'[USB Serial] Failed to open $candidate: ${error.msg} (code ${error.error})',
|
'[USB Serial] Failed to open $candidate: ${error.msg} (code ${error.error})',
|
||||||
);
|
);
|
||||||
@@ -314,7 +302,6 @@ class UsbSerialService {
|
|||||||
// crashes with "Callback invoked after it has been deleted".
|
// crashes with "Callback invoked after it has been deleted".
|
||||||
final serial = _serial;
|
final serial = _serial;
|
||||||
_serial = null;
|
_serial = null;
|
||||||
_lastSerial = null;
|
|
||||||
try {
|
try {
|
||||||
if (serial?.isOpen() == FlOpenStatus.open) {
|
if (serial?.isOpen() == FlOpenStatus.open) {
|
||||||
serial?.closePort();
|
serial?.closePort();
|
||||||
@@ -322,9 +309,9 @@ class UsbSerialService {
|
|||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Ignore errors while closing.
|
// Ignore errors while closing.
|
||||||
}
|
}
|
||||||
try {
|
// Note: we do NOT call free() here; that would globally reset native
|
||||||
serial?.free();
|
// state for all ports. The global reset is done in connect() instead,
|
||||||
} catch (_) {}
|
// before the next open, which is the safer place to do it.
|
||||||
|
|
||||||
// Now it is safe to cancel the Dart subscription — the native thread has
|
// Now it is safe to cancel the Dart subscription — the native thread has
|
||||||
// already seen the port close and will not fire any more callbacks.
|
// already seen the port close and will not fire any more callbacks.
|
||||||
@@ -350,6 +337,25 @@ class UsbSerialService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
// Synchronously close the native port so the SerialThread exits before
|
||||||
|
// the Dart isolate is torn down (e.g. on hot restart). The async
|
||||||
|
// disconnect() path via unawaited() offers no ordering guarantee — the
|
||||||
|
// isolate may die before the Future resolves, leaving the thread alive
|
||||||
|
// with a dangling NativeCallable pointer.
|
||||||
|
if (_useDesktopFlSerial) {
|
||||||
|
final serial = _serial;
|
||||||
|
_serial = null;
|
||||||
|
_status = UsbSerialStatus.disconnected;
|
||||||
|
_connectedPortKey = null;
|
||||||
|
_connectedPortLabel = null;
|
||||||
|
try {
|
||||||
|
if (serial?.isOpen() == FlOpenStatus.open) {
|
||||||
|
serial?.closePort(); // synchronous C call — kills the SerialThread
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
// Kick off the full async teardown for anything else (subscription cancel,
|
||||||
|
// stream controller close). These are best-effort at dispose time.
|
||||||
unawaited(disconnect().whenComplete(_closeFrameController));
|
unawaited(disconnect().whenComplete(_closeFrameController));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user