feat(core): A.6.1 — use OS connectivity signals to drive reconnect
Extends the A.6 supervisor with an OS-level connectivity hint so a returning network triggers a redial immediately instead of waiting out the current backoff slot (up to 60 s). The watchdog remains the authoritative loss detector — the OS signal is advisory. * `chanora_core::NetworkState` (Unknown / Online / Offline) is owned by `ChanoraSession` via a `tokio::sync::watch::Sender`. `set_network_state()` / `network_state()` are the public accessors. * The supervisor's watch-phase `select!` gains a `network_rx` branch: Offline pre-charges watchdog misses (capped at `MAX_MISSES - 1`) so the next probe failure trips immediately; Online clears stale misses. This shrinks UI-banner latency on a Wi-Fi drop from ~15 s to ~5 s. * The reconnect-loop's backoff sleep races against Online: a transition cuts the sleep short and resets the attempt counter so future losses start at the smallest backoff window again. * `chanora_bridge` adds `BridgeNetworkState` (mirror enum) and a sync `set_network_state(state)` function. On platforms with no signal wired the supervisor stays at Unknown and falls back to pure watchdog/backoff — no behavioural regression vs A.6. * Flutter adds `connectivity_plus ^6.1.0` and wires `_wireConnectivity()` in `main()`: seeds with `checkConnectivity()` then forwards every `onConnectivityChanged` to the bridge, mapping any non-`none` transport to Online. Verified on Moto G Stylus 5G (Android 14): `svc wifi disable && svc data disable` for ~40 s — reconnect banner appeared promptly because the watchdog was pre-charged. After `svc wifi enable && svc data enable` the supervisor woke from its 15 s backoff slot and reconnected within seconds; the channel tree re-rendered without user action.
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'l10n/generated/app_localizations.dart';
|
import 'l10n/generated/app_localizations.dart';
|
||||||
@@ -20,9 +21,42 @@ import 'src/rust/frb_generated.dart';
|
|||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await RustLib.init();
|
await RustLib.init();
|
||||||
|
// Push the OS-reported connectivity state into the core supervisor.
|
||||||
|
// The supervisor uses this to short-circuit reconnect backoff when
|
||||||
|
// the network comes back, and to pre-charge the loss watchdog when
|
||||||
|
// the OS already knows we're offline (A.6.1).
|
||||||
|
unawaited(_wireConnectivity());
|
||||||
runApp(const ChanoraApp());
|
runApp(const ChanoraApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Map `connectivity_plus`' list-of-results to our coarse tri-state.
|
||||||
|
/// We consider the device "Online" if any of the reported transports
|
||||||
|
/// is non-`none`. This is intentionally permissive — the supervisor's
|
||||||
|
/// watchdog still verifies reachability against the actual server.
|
||||||
|
rust.BridgeNetworkState _mapConnectivity(List<ConnectivityResult> results) {
|
||||||
|
if (results.isEmpty) return rust.BridgeNetworkState.unknown;
|
||||||
|
final allNone = results.every((r) => r == ConnectivityResult.none);
|
||||||
|
if (allNone) return rust.BridgeNetworkState.offline;
|
||||||
|
return rust.BridgeNetworkState.online;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _wireConnectivity() async {
|
||||||
|
final connectivity = Connectivity();
|
||||||
|
// Seed with the current value so the supervisor has a real reading
|
||||||
|
// before the first transition.
|
||||||
|
try {
|
||||||
|
final initial = await connectivity.checkConnectivity();
|
||||||
|
rust.setNetworkState(state: _mapConnectivity(initial));
|
||||||
|
} catch (_) {
|
||||||
|
// Best effort; if the plugin isn't available on this platform
|
||||||
|
// we stay at Unknown and the supervisor falls back to its
|
||||||
|
// watchdog-only behaviour.
|
||||||
|
}
|
||||||
|
connectivity.onConnectivityChanged.listen((results) {
|
||||||
|
rust.setNetworkState(state: _mapConnectivity(results));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class ChanoraApp extends StatelessWidget {
|
class ChanoraApp extends StatelessWidget {
|
||||||
const ChanoraApp({super.key});
|
const ChanoraApp({super.key});
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import 'package:freezed_annotation/freezed_annotation.dart' hide protected;
|
|||||||
part 'api.freezed.dart';
|
part 'api.freezed.dart';
|
||||||
|
|
||||||
// These functions are ignored because they are not marked as `pub`: `runtime`, `session`
|
// These functions are ignored because they are not marked as `pub`: `runtime`, `session`
|
||||||
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `clone`, `clone`, `clone`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `from`, `from`
|
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `from`, `from`, `from`
|
||||||
|
|
||||||
/// Connect to a TeamSpeak-compatible server and return the initial
|
/// Connect to a TeamSpeak-compatible server and return the initial
|
||||||
/// state snapshot. Honours the DEC-006 single-connection invariant
|
/// state snapshot. Honours the DEC-006 single-connection invariant
|
||||||
@@ -37,6 +37,13 @@ Future<void> startAudio() => RustLib.instance.api.crateApiStartAudio();
|
|||||||
Future<void> setPtt({required bool active}) =>
|
Future<void> setPtt({required bool active}) =>
|
||||||
RustLib.instance.api.crateApiSetPtt(active: active);
|
RustLib.instance.api.crateApiSetPtt(active: active);
|
||||||
|
|
||||||
|
/// Notify the core of the latest OS-reported connectivity state.
|
||||||
|
/// Called by the Flutter side from `connectivity_plus` callbacks.
|
||||||
|
/// The core's supervisor uses this to (a) pre-charge the watchdog
|
||||||
|
/// on Offline and (b) short-circuit reconnect backoff on Online.
|
||||||
|
void setNetworkState({required BridgeNetworkState state}) =>
|
||||||
|
RustLib.instance.api.crateApiSetNetworkState(state: state);
|
||||||
|
|
||||||
/// Subscribe to lifecycle events. Each call yields a fresh
|
/// Subscribe to lifecycle events. Each call yields a fresh
|
||||||
/// subscription; multiple subscribers are supported. On slow
|
/// subscription; multiple subscribers are supported. On slow
|
||||||
/// consumers, events are dropped rather than blocking the supervisor
|
/// consumers, events are dropped rather than blocking the supervisor
|
||||||
@@ -185,6 +192,19 @@ sealed class BridgeEvent with _$BridgeEvent {
|
|||||||
const factory BridgeEvent.audioStopped() = BridgeEvent_AudioStopped;
|
const factory BridgeEvent.audioStopped() = BridgeEvent_AudioStopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Coarse OS-reported network state. Mirrors
|
||||||
|
/// [`chanora_core::NetworkState`] across the bridge.
|
||||||
|
enum BridgeNetworkState {
|
||||||
|
/// No signal seen yet.
|
||||||
|
unknown,
|
||||||
|
|
||||||
|
/// OS reports a usable network.
|
||||||
|
online,
|
||||||
|
|
||||||
|
/// OS reports no network.
|
||||||
|
offline,
|
||||||
|
}
|
||||||
|
|
||||||
/// Server snapshot as seen by Dart.
|
/// Server snapshot as seen by Dart.
|
||||||
class BridgeSnapshot {
|
class BridgeSnapshot {
|
||||||
/// Server name.
|
/// Server name.
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
|||||||
String get codegenVersion => '2.12.0';
|
String get codegenVersion => '2.12.0';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get rustContentHash => -1896742393;
|
int get rustContentHash => -1212711005;
|
||||||
|
|
||||||
static const kDefaultExternalLibraryLoaderConfig =
|
static const kDefaultExternalLibraryLoaderConfig =
|
||||||
ExternalLibraryLoaderConfig(
|
ExternalLibraryLoaderConfig(
|
||||||
@@ -94,6 +94,8 @@ abstract class RustLibApi extends BaseApi {
|
|||||||
|
|
||||||
Future<bool> crateApiIsConnected();
|
Future<bool> crateApiIsConnected();
|
||||||
|
|
||||||
|
void crateApiSetNetworkState({required BridgeNetworkState state});
|
||||||
|
|
||||||
Future<void> crateApiSetPtt({required bool active});
|
Future<void> crateApiSetPtt({required bool active});
|
||||||
|
|
||||||
Future<BridgeSnapshot> crateApiSnapshot();
|
Future<BridgeSnapshot> crateApiSnapshot();
|
||||||
@@ -281,6 +283,29 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
TaskConstMeta get kCrateApiIsConnectedConstMeta =>
|
TaskConstMeta get kCrateApiIsConnectedConstMeta =>
|
||||||
const TaskConstMeta(debugName: "is_connected", argNames: []);
|
const TaskConstMeta(debugName: "is_connected", argNames: []);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void crateApiSetNetworkState({required BridgeNetworkState state}) {
|
||||||
|
return handler.executeSync(
|
||||||
|
SyncTask(
|
||||||
|
callFfi: () {
|
||||||
|
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||||
|
sse_encode_bridge_network_state(state, serializer);
|
||||||
|
return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 7)!;
|
||||||
|
},
|
||||||
|
codec: SseCodec(
|
||||||
|
decodeSuccessData: sse_decode_unit,
|
||||||
|
decodeErrorData: null,
|
||||||
|
),
|
||||||
|
constMeta: kCrateApiSetNetworkStateConstMeta,
|
||||||
|
argValues: [state],
|
||||||
|
apiImpl: this,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskConstMeta get kCrateApiSetNetworkStateConstMeta =>
|
||||||
|
const TaskConstMeta(debugName: "set_network_state", argNames: ["state"]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> crateApiSetPtt({required bool active}) {
|
Future<void> crateApiSetPtt({required bool active}) {
|
||||||
return handler.executeNormal(
|
return handler.executeNormal(
|
||||||
@@ -291,7 +316,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 7,
|
funcId: 8,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -318,7 +343,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 8,
|
funcId: 9,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -345,7 +370,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 9,
|
funcId: 10,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -477,6 +502,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
BridgeNetworkState dco_decode_bridge_network_state(dynamic raw) {
|
||||||
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
|
return BridgeNetworkState.values[raw as int];
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
BridgeSnapshot dco_decode_bridge_snapshot(dynamic raw) {
|
BridgeSnapshot dco_decode_bridge_snapshot(dynamic raw) {
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
@@ -493,6 +524,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
int dco_decode_i_32(dynamic raw) {
|
||||||
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
|
return raw as int;
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64 dco_decode_i_64(dynamic raw) {
|
PlatformInt64 dco_decode_i_64(dynamic raw) {
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
@@ -665,6 +702,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
BridgeNetworkState sse_decode_bridge_network_state(
|
||||||
|
SseDeserializer deserializer,
|
||||||
|
) {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
var inner = sse_decode_i_32(deserializer);
|
||||||
|
return BridgeNetworkState.values[inner];
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
BridgeSnapshot sse_decode_bridge_snapshot(SseDeserializer deserializer) {
|
BridgeSnapshot sse_decode_bridge_snapshot(SseDeserializer deserializer) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
@@ -684,6 +730,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
int sse_decode_i_32(SseDeserializer deserializer) {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
return deserializer.buffer.getInt32();
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer) {
|
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
@@ -748,12 +800,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
|
||||||
int sse_decode_i_32(SseDeserializer deserializer) {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
return deserializer.buffer.getInt32();
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_AnyhowException(
|
void sse_encode_AnyhowException(
|
||||||
AnyhowException self,
|
AnyhowException self,
|
||||||
@@ -871,6 +917,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_bridge_network_state(
|
||||||
|
BridgeNetworkState self,
|
||||||
|
SseSerializer serializer,
|
||||||
|
) {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
sse_encode_i_32(self.index, serializer);
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_bridge_snapshot(
|
void sse_encode_bridge_snapshot(
|
||||||
BridgeSnapshot self,
|
BridgeSnapshot self,
|
||||||
@@ -885,6 +940,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
sse_encode_list_bridge_client(self.clients, serializer);
|
sse_encode_list_bridge_client(self.clients, serializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_i_32(int self, SseSerializer serializer) {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
serializer.buffer.putInt32(self);
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer) {
|
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
@@ -947,10 +1008,4 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
void sse_encode_unit(void self, SseSerializer serializer) {
|
void sse_encode_unit(void self, SseSerializer serializer) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_i_32(int self, SseSerializer serializer) {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
serializer.buffer.putInt32(self);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,9 +48,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||||||
@protected
|
@protected
|
||||||
BridgeEvent dco_decode_bridge_event(dynamic raw);
|
BridgeEvent dco_decode_bridge_event(dynamic raw);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
BridgeNetworkState dco_decode_bridge_network_state(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
BridgeSnapshot dco_decode_bridge_snapshot(dynamic raw);
|
BridgeSnapshot dco_decode_bridge_snapshot(dynamic raw);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
int dco_decode_i_32(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
PlatformInt64 dco_decode_i_64(dynamic raw);
|
||||||
|
|
||||||
@@ -104,9 +110,17 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||||||
@protected
|
@protected
|
||||||
BridgeEvent sse_decode_bridge_event(SseDeserializer deserializer);
|
BridgeEvent sse_decode_bridge_event(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
BridgeNetworkState sse_decode_bridge_network_state(
|
||||||
|
SseDeserializer deserializer,
|
||||||
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
BridgeSnapshot sse_decode_bridge_snapshot(SseDeserializer deserializer);
|
BridgeSnapshot sse_decode_bridge_snapshot(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
int sse_decode_i_32(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
||||||
|
|
||||||
@@ -135,9 +149,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||||||
@protected
|
@protected
|
||||||
void sse_decode_unit(SseDeserializer deserializer);
|
void sse_decode_unit(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
|
||||||
int sse_decode_i_32(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_AnyhowException(
|
void sse_encode_AnyhowException(
|
||||||
AnyhowException self,
|
AnyhowException self,
|
||||||
@@ -174,12 +185,21 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||||||
@protected
|
@protected
|
||||||
void sse_encode_bridge_event(BridgeEvent self, SseSerializer serializer);
|
void sse_encode_bridge_event(BridgeEvent self, SseSerializer serializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_bridge_network_state(
|
||||||
|
BridgeNetworkState self,
|
||||||
|
SseSerializer serializer,
|
||||||
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_bridge_snapshot(
|
void sse_encode_bridge_snapshot(
|
||||||
BridgeSnapshot self,
|
BridgeSnapshot self,
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_i_32(int self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
||||||
|
|
||||||
@@ -212,9 +232,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_unit(void self, SseSerializer serializer);
|
void sse_encode_unit(void self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_i_32(int self, SseSerializer serializer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Section: wire_class
|
// Section: wire_class
|
||||||
|
|||||||
@@ -50,9 +50,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||||||
@protected
|
@protected
|
||||||
BridgeEvent dco_decode_bridge_event(dynamic raw);
|
BridgeEvent dco_decode_bridge_event(dynamic raw);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
BridgeNetworkState dco_decode_bridge_network_state(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
BridgeSnapshot dco_decode_bridge_snapshot(dynamic raw);
|
BridgeSnapshot dco_decode_bridge_snapshot(dynamic raw);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
int dco_decode_i_32(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
PlatformInt64 dco_decode_i_64(dynamic raw);
|
||||||
|
|
||||||
@@ -106,9 +112,17 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||||||
@protected
|
@protected
|
||||||
BridgeEvent sse_decode_bridge_event(SseDeserializer deserializer);
|
BridgeEvent sse_decode_bridge_event(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
BridgeNetworkState sse_decode_bridge_network_state(
|
||||||
|
SseDeserializer deserializer,
|
||||||
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
BridgeSnapshot sse_decode_bridge_snapshot(SseDeserializer deserializer);
|
BridgeSnapshot sse_decode_bridge_snapshot(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
int sse_decode_i_32(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
||||||
|
|
||||||
@@ -137,9 +151,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||||||
@protected
|
@protected
|
||||||
void sse_decode_unit(SseDeserializer deserializer);
|
void sse_decode_unit(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
|
||||||
int sse_decode_i_32(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_AnyhowException(
|
void sse_encode_AnyhowException(
|
||||||
AnyhowException self,
|
AnyhowException self,
|
||||||
@@ -176,12 +187,21 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||||||
@protected
|
@protected
|
||||||
void sse_encode_bridge_event(BridgeEvent self, SseSerializer serializer);
|
void sse_encode_bridge_event(BridgeEvent self, SseSerializer serializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_bridge_network_state(
|
||||||
|
BridgeNetworkState self,
|
||||||
|
SseSerializer serializer,
|
||||||
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_bridge_snapshot(
|
void sse_encode_bridge_snapshot(
|
||||||
BridgeSnapshot self,
|
BridgeSnapshot self,
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_i_32(int self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
||||||
|
|
||||||
@@ -214,9 +234,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_unit(void self, SseSerializer serializer);
|
void sse_encode_unit(void self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_i_32(int self, SseSerializer serializer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Section: wire_class
|
// Section: wire_class
|
||||||
|
|||||||
@@ -129,6 +129,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
version: "1.19.1"
|
||||||
|
connectivity_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: connectivity_plus
|
||||||
|
sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.5"
|
||||||
|
connectivity_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: connectivity_plus_platform_interface
|
||||||
|
sha256: "3c09627c536d22fd24691a905cdd8b14520de69da52c7a97499c8be5284a32ed"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
convert:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -161,6 +177,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.7"
|
version: "3.1.7"
|
||||||
|
dbus:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dbus
|
||||||
|
sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.12"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -169,6 +193,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.3"
|
version: "1.3.3"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -216,6 +248,11 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
freezed:
|
freezed:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -360,6 +397,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
|
nm:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: nm
|
||||||
|
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.0"
|
||||||
package_config:
|
package_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -376,6 +421,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
|
petitparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: petitparser
|
||||||
|
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.2"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.8"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -541,6 +602,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.3"
|
||||||
|
xml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xml
|
||||||
|
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.6.1"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
flutter_rust_bridge: 2.12.0
|
flutter_rust_bridge: 2.12.0
|
||||||
freezed_annotation: ^3.1.0
|
freezed_annotation: ^3.1.0
|
||||||
|
connectivity_plus: ^6.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
+107
-16
@@ -41,7 +41,7 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::sync::{broadcast, oneshot, Mutex};
|
use tokio::sync::{broadcast, oneshot, watch, Mutex};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
@@ -121,6 +121,20 @@ pub enum SessionEvent {
|
|||||||
AudioStopped,
|
AudioStopped,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Coarse OS-reported network state. Populated by the Flutter side
|
||||||
|
/// via `connectivity_plus`; on platforms where no signal is wired
|
||||||
|
/// we stay at `Unknown` forever and the supervisor falls back to
|
||||||
|
/// pure watchdog/backoff behaviour.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum NetworkState {
|
||||||
|
/// No signal seen yet — treat as ambiguous; don't change behaviour.
|
||||||
|
Unknown,
|
||||||
|
/// OS reports at least one network with internet capability.
|
||||||
|
Online,
|
||||||
|
/// OS reports no networks available.
|
||||||
|
Offline,
|
||||||
|
}
|
||||||
|
|
||||||
/// Channel capacity for the broadcast events. Generous because
|
/// Channel capacity for the broadcast events. Generous because
|
||||||
/// reconnect cycles emit several events per attempt; if subscribers
|
/// reconnect cycles emit several events per attempt; if subscribers
|
||||||
/// fall behind we'd rather skip than block the supervisor.
|
/// fall behind we'd rather skip than block the supervisor.
|
||||||
@@ -161,18 +175,39 @@ struct ConnectedState {
|
|||||||
pub struct ChanoraSession {
|
pub struct ChanoraSession {
|
||||||
inner: Arc<Mutex<Option<ConnectedState>>>,
|
inner: Arc<Mutex<Option<ConnectedState>>>,
|
||||||
events_tx: broadcast::Sender<SessionEvent>,
|
events_tx: broadcast::Sender<SessionEvent>,
|
||||||
|
/// OS-reported network state. Updated by the bridge from
|
||||||
|
/// `connectivity_plus` callbacks. Supervisor observes via
|
||||||
|
/// [`watch::Receiver`].
|
||||||
|
network_tx: watch::Sender<NetworkState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChanoraSession {
|
impl ChanoraSession {
|
||||||
/// Construct an empty session. Performs no I/O.
|
/// Construct an empty session. Performs no I/O.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let (events_tx, _) = broadcast::channel(EVENT_CHANNEL_CAPACITY);
|
let (events_tx, _) = broadcast::channel(EVENT_CHANNEL_CAPACITY);
|
||||||
|
let (network_tx, _) = watch::channel(NetworkState::Unknown);
|
||||||
Self {
|
Self {
|
||||||
inner: Arc::new(Mutex::new(None)),
|
inner: Arc::new(Mutex::new(None)),
|
||||||
events_tx,
|
events_tx,
|
||||||
|
network_tx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Push an OS connectivity update. Called by the bridge when
|
||||||
|
/// `connectivity_plus` fires. Safe to call from any thread.
|
||||||
|
pub fn set_network_state(&self, state: NetworkState) {
|
||||||
|
// `send_if_modified` would suppress duplicate sends, but
|
||||||
|
// `watch::Sender::send` already drops sends with no
|
||||||
|
// subscribers gracefully. Using `send_replace` so the value
|
||||||
|
// is updated even before any subscriber attaches.
|
||||||
|
let _ = self.network_tx.send_replace(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the latest OS connectivity state. Useful for diagnostics.
|
||||||
|
pub fn network_state(&self) -> NetworkState {
|
||||||
|
*self.network_tx.borrow()
|
||||||
|
}
|
||||||
|
|
||||||
/// Subscribe to lifecycle events. The returned receiver fires
|
/// Subscribe to lifecycle events. The returned receiver fires
|
||||||
/// on connect / lost / reconnecting / disconnected /
|
/// on connect / lost / reconnecting / disconnected /
|
||||||
/// audio-started / audio-stopped transitions. Multiple
|
/// audio-started / audio-stopped transitions. Multiple
|
||||||
@@ -213,6 +248,7 @@ impl ChanoraSession {
|
|||||||
probe,
|
probe,
|
||||||
cancel_rx,
|
cancel_rx,
|
||||||
sup_inner.clone(),
|
sup_inner.clone(),
|
||||||
|
self.network_tx.subscribe(),
|
||||||
));
|
));
|
||||||
|
|
||||||
let _ = self.events_tx.send(SessionEvent::Connected {
|
let _ = self.events_tx.send(SessionEvent::Connected {
|
||||||
@@ -355,6 +391,7 @@ async fn supervisor_loop(
|
|||||||
initial_probe: chanora_protocol::SnapshotProbe,
|
initial_probe: chanora_protocol::SnapshotProbe,
|
||||||
mut cancel_rx: oneshot::Receiver<()>,
|
mut cancel_rx: oneshot::Receiver<()>,
|
||||||
sup_inner: Arc<Mutex<SupervisorInner>>,
|
sup_inner: Arc<Mutex<SupervisorInner>>,
|
||||||
|
mut network_rx: watch::Receiver<NetworkState>,
|
||||||
) {
|
) {
|
||||||
let mut lost_rx = initial_lost_rx;
|
let mut lost_rx = initial_lost_rx;
|
||||||
let mut probe = initial_probe;
|
let mut probe = initial_probe;
|
||||||
@@ -390,6 +427,39 @@ async fn supervisor_loop(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
changed = network_rx.changed() => {
|
||||||
|
if changed.is_err() {
|
||||||
|
// The session was dropped; nothing more to do.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let new_state = *network_rx.borrow_and_update();
|
||||||
|
if new_state == NetworkState::Offline {
|
||||||
|
// OS says we're offline. Don't fabricate a
|
||||||
|
// loss outright — captive portals and
|
||||||
|
// transient flicker can produce false
|
||||||
|
// negatives — but pre-charge the watchdog
|
||||||
|
// so the next probe failure trips it
|
||||||
|
// immediately. This shrinks UI latency from
|
||||||
|
// ~15s to ~5s in the common case.
|
||||||
|
misses = misses.saturating_add(1).min(WATCHDOG_MAX_MISSES - 1);
|
||||||
|
info!(
|
||||||
|
target: "chanora_core",
|
||||||
|
misses,
|
||||||
|
"connectivity: OS reports offline; pre-charging watchdog"
|
||||||
|
);
|
||||||
|
} else if new_state == NetworkState::Online {
|
||||||
|
// Reset on confirmed online so we don't
|
||||||
|
// carry stale Offline charges into the
|
||||||
|
// healthy state.
|
||||||
|
if misses > 0 {
|
||||||
|
info!(
|
||||||
|
target: "chanora_core",
|
||||||
|
"connectivity: OS reports online; clearing watchdog misses"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
misses = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
_ = watchdog.tick() => {
|
_ = watchdog.tick() => {
|
||||||
match tokio::time::timeout(WATCHDOG_PROBE_TIMEOUT, probe.probe()).await {
|
match tokio::time::timeout(WATCHDOG_PROBE_TIMEOUT, probe.probe()).await {
|
||||||
Ok(Ok(_)) => {
|
Ok(Ok(_)) => {
|
||||||
@@ -426,14 +496,6 @@ async fn supervisor_loop(
|
|||||||
misses,
|
misses,
|
||||||
"watchdog: declaring connection lost"
|
"watchdog: declaring connection lost"
|
||||||
);
|
);
|
||||||
// Replace the dead protocol client with a
|
|
||||||
// dropped slot so the reconnect path below
|
|
||||||
// doesn't accidentally keep using it. We
|
|
||||||
// also stop audio here (the reconnect path
|
|
||||||
// does this too, but doing it now ensures
|
|
||||||
// the mic / playback engine stops talking
|
|
||||||
// to a stale voice_out_tx as quickly as
|
|
||||||
// possible).
|
|
||||||
break chanora_protocol::DisconnectReason::Error(
|
break chanora_protocol::DisconnectReason::Error(
|
||||||
"watchdog: server stopped responding".to_string(),
|
"watchdog: server stopped responding".to_string(),
|
||||||
);
|
);
|
||||||
@@ -490,17 +552,46 @@ async fn supervisor_loop(
|
|||||||
"reconnect: sleeping before next attempt"
|
"reconnect: sleeping before next attempt"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Sleep with cancellation support.
|
// Sleep with cancellation support. An OS
|
||||||
let slept = tokio::select! {
|
// "online" notification short-circuits the
|
||||||
|
// sleep and resets the attempt counter so the
|
||||||
|
// next outage starts with the smallest backoff
|
||||||
|
// window again.
|
||||||
|
enum SleepOutcome { Elapsed, NetworkUp, Cancelled }
|
||||||
|
let outcome = tokio::select! {
|
||||||
biased;
|
biased;
|
||||||
_ = &mut cancel_rx => {
|
_ = &mut cancel_rx => SleepOutcome::Cancelled,
|
||||||
|
changed = network_rx.changed() => {
|
||||||
|
if changed.is_err() {
|
||||||
|
SleepOutcome::Cancelled
|
||||||
|
} else if *network_rx.borrow_and_update() == NetworkState::Online {
|
||||||
|
SleepOutcome::NetworkUp
|
||||||
|
} else {
|
||||||
|
// Offline / Unknown transition — keep waiting the rest of the slot.
|
||||||
|
tokio::select! {
|
||||||
|
_ = &mut cancel_rx => SleepOutcome::Cancelled,
|
||||||
|
_ = tokio::time::sleep(Duration::from_secs(delay_secs as u64)) => SleepOutcome::Elapsed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = tokio::time::sleep(Duration::from_secs(delay_secs as u64)) => SleepOutcome::Elapsed,
|
||||||
|
};
|
||||||
|
match outcome {
|
||||||
|
SleepOutcome::Cancelled => {
|
||||||
info!(target: "chanora_core", "supervisor cancelled during backoff");
|
info!(target: "chanora_core", "supervisor cancelled during backoff");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_ = tokio::time::sleep(Duration::from_secs(delay_secs as u64)) => true,
|
SleepOutcome::NetworkUp => {
|
||||||
};
|
info!(
|
||||||
if !slept {
|
target: "chanora_core",
|
||||||
return;
|
attempt,
|
||||||
|
"connectivity: OS reports online; redialling immediately"
|
||||||
|
);
|
||||||
|
// Reset attempt counter so future losses
|
||||||
|
// start the backoff schedule fresh.
|
||||||
|
attempt = 0;
|
||||||
|
}
|
||||||
|
SleepOutcome::Elapsed => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
info!(target: "chanora_core", attempt, "reconnect: dialling");
|
info!(target: "chanora_core", attempt, "reconnect: dialling");
|
||||||
|
|||||||
@@ -233,6 +233,39 @@ pub struct BridgeAudioStats {
|
|||||||
pub ptt_active: bool,
|
pub ptt_active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- Connectivity (A.6.1) ----------
|
||||||
|
|
||||||
|
/// Coarse OS-reported network state. Mirrors
|
||||||
|
/// [`chanora_core::NetworkState`] across the bridge.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum BridgeNetworkState {
|
||||||
|
/// No signal seen yet.
|
||||||
|
Unknown,
|
||||||
|
/// OS reports a usable network.
|
||||||
|
Online,
|
||||||
|
/// OS reports no network.
|
||||||
|
Offline,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BridgeNetworkState> for chanora_core::NetworkState {
|
||||||
|
fn from(s: BridgeNetworkState) -> Self {
|
||||||
|
match s {
|
||||||
|
BridgeNetworkState::Unknown => chanora_core::NetworkState::Unknown,
|
||||||
|
BridgeNetworkState::Online => chanora_core::NetworkState::Online,
|
||||||
|
BridgeNetworkState::Offline => chanora_core::NetworkState::Offline,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify the core of the latest OS-reported connectivity state.
|
||||||
|
/// Called by the Flutter side from `connectivity_plus` callbacks.
|
||||||
|
/// The core's supervisor uses this to (a) pre-charge the watchdog
|
||||||
|
/// on Offline and (b) short-circuit reconnect backoff on Online.
|
||||||
|
#[frb(sync)]
|
||||||
|
pub fn set_network_state(state: BridgeNetworkState) {
|
||||||
|
session().set_network_state(state.into());
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- Events (A.6) ----------
|
// ---------- Events (A.6) ----------
|
||||||
|
|
||||||
/// Lifecycle event surfaced to Dart. Schema-controlled mirror of
|
/// Lifecycle event surfaced to Dart. Schema-controlled mirror of
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
|
|||||||
default_rust_auto_opaque = RustAutoOpaqueMoi,
|
default_rust_auto_opaque = RustAutoOpaqueMoi,
|
||||||
);
|
);
|
||||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
|
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
|
||||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1896742393;
|
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1212711005;
|
||||||
|
|
||||||
// Section: executor
|
// Section: executor
|
||||||
|
|
||||||
@@ -258,6 +258,38 @@ fn wire__crate__api__is_connected_impl(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
fn wire__crate__api__set_network_state_impl(
|
||||||
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
rust_vec_len_: i32,
|
||||||
|
data_len_: i32,
|
||||||
|
) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
|
||||||
|
FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::<flutter_rust_bridge::for_generated::SseCodec, _>(
|
||||||
|
flutter_rust_bridge::for_generated::TaskInfo {
|
||||||
|
debug_name: "set_network_state",
|
||||||
|
port: None,
|
||||||
|
mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync,
|
||||||
|
},
|
||||||
|
move || {
|
||||||
|
let message = unsafe {
|
||||||
|
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
|
||||||
|
ptr_,
|
||||||
|
rust_vec_len_,
|
||||||
|
data_len_,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let mut deserializer =
|
||||||
|
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||||
|
let api_state = <crate::api::BridgeNetworkState>::sse_decode(&mut deserializer);
|
||||||
|
deserializer.end();
|
||||||
|
transform_result_sse::<_, ()>((move || {
|
||||||
|
let output_ok = Result::<_, ()>::Ok({
|
||||||
|
crate::api::set_network_state(api_state);
|
||||||
|
})?;
|
||||||
|
Ok(output_ok)
|
||||||
|
})())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
fn wire__crate__api__set_ptt_impl(
|
fn wire__crate__api__set_ptt_impl(
|
||||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
@@ -522,6 +554,19 @@ impl SseDecode for crate::api::BridgeEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SseDecode for crate::api::BridgeNetworkState {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
let mut inner = <i32>::sse_decode(deserializer);
|
||||||
|
return match inner {
|
||||||
|
0 => crate::api::BridgeNetworkState::Unknown,
|
||||||
|
1 => crate::api::BridgeNetworkState::Online,
|
||||||
|
2 => crate::api::BridgeNetworkState::Offline,
|
||||||
|
_ => unreachable!("Invalid variant for BridgeNetworkState: {}", inner),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SseDecode for crate::api::BridgeSnapshot {
|
impl SseDecode for crate::api::BridgeSnapshot {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
@@ -542,6 +587,13 @@ impl SseDecode for crate::api::BridgeSnapshot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SseDecode for i32 {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
deserializer.cursor.read_i32::<NativeEndian>().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SseDecode for i64 {
|
impl SseDecode for i64 {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
@@ -611,13 +663,6 @@ impl SseDecode for () {
|
|||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {}
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseDecode for i32 {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
|
||||||
deserializer.cursor.read_i32::<NativeEndian>().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pde_ffi_dispatcher_primary_impl(
|
fn pde_ffi_dispatcher_primary_impl(
|
||||||
func_id: i32,
|
func_id: i32,
|
||||||
port: flutter_rust_bridge::for_generated::MessagePort,
|
port: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
@@ -633,9 +678,9 @@ fn pde_ffi_dispatcher_primary_impl(
|
|||||||
4 => wire__crate__api__disconnect_impl(port, ptr, rust_vec_len, data_len),
|
4 => wire__crate__api__disconnect_impl(port, ptr, rust_vec_len, data_len),
|
||||||
5 => wire__crate__api__events_stream_impl(port, ptr, rust_vec_len, data_len),
|
5 => wire__crate__api__events_stream_impl(port, ptr, rust_vec_len, data_len),
|
||||||
6 => wire__crate__api__is_connected_impl(port, ptr, rust_vec_len, data_len),
|
6 => wire__crate__api__is_connected_impl(port, ptr, rust_vec_len, data_len),
|
||||||
7 => wire__crate__api__set_ptt_impl(port, ptr, rust_vec_len, data_len),
|
8 => wire__crate__api__set_ptt_impl(port, ptr, rust_vec_len, data_len),
|
||||||
8 => wire__crate__api__snapshot_impl(port, ptr, rust_vec_len, data_len),
|
9 => wire__crate__api__snapshot_impl(port, ptr, rust_vec_len, data_len),
|
||||||
9 => wire__crate__api__start_audio_impl(port, ptr, rust_vec_len, data_len),
|
10 => wire__crate__api__start_audio_impl(port, ptr, rust_vec_len, data_len),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -648,6 +693,7 @@ fn pde_ffi_dispatcher_sync_impl(
|
|||||||
) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
|
) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
|
||||||
// Codec=Pde (Serialization + dispatch), see doc to use other codecs
|
// Codec=Pde (Serialization + dispatch), see doc to use other codecs
|
||||||
match func_id {
|
match func_id {
|
||||||
|
7 => wire__crate__api__set_network_state_impl(ptr, rust_vec_len, data_len),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -778,6 +824,28 @@ impl flutter_rust_bridge::IntoIntoDart<crate::api::BridgeEvent> for crate::api::
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
|
impl flutter_rust_bridge::IntoDart for crate::api::BridgeNetworkState {
|
||||||
|
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||||
|
match self {
|
||||||
|
Self::Unknown => 0.into_dart(),
|
||||||
|
Self::Online => 1.into_dart(),
|
||||||
|
Self::Offline => 2.into_dart(),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
|
||||||
|
for crate::api::BridgeNetworkState
|
||||||
|
{
|
||||||
|
}
|
||||||
|
impl flutter_rust_bridge::IntoIntoDart<crate::api::BridgeNetworkState>
|
||||||
|
for crate::api::BridgeNetworkState
|
||||||
|
{
|
||||||
|
fn into_into_dart(self) -> crate::api::BridgeNetworkState {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
impl flutter_rust_bridge::IntoDart for crate::api::BridgeSnapshot {
|
impl flutter_rust_bridge::IntoDart for crate::api::BridgeSnapshot {
|
||||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||||
[
|
[
|
||||||
@@ -927,6 +995,23 @@ impl SseEncode for crate::api::BridgeEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SseEncode for crate::api::BridgeNetworkState {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
<i32>::sse_encode(
|
||||||
|
match self {
|
||||||
|
crate::api::BridgeNetworkState::Unknown => 0,
|
||||||
|
crate::api::BridgeNetworkState::Online => 1,
|
||||||
|
crate::api::BridgeNetworkState::Offline => 2,
|
||||||
|
_ => {
|
||||||
|
unimplemented!("");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
serializer,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SseEncode for crate::api::BridgeSnapshot {
|
impl SseEncode for crate::api::BridgeSnapshot {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
@@ -939,6 +1024,13 @@ impl SseEncode for crate::api::BridgeSnapshot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SseEncode for i32 {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
serializer.cursor.write_i32::<NativeEndian>(self).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SseEncode for i64 {
|
impl SseEncode for i64 {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
@@ -1002,13 +1094,6 @@ impl SseEncode for () {
|
|||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {}
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseEncode for i32 {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
|
||||||
serializer.cursor.write_i32::<NativeEndian>(self).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
mod io {
|
mod io {
|
||||||
// This file is automatically generated, so please do not edit it.
|
// This file is automatically generated, so please do not edit it.
|
||||||
|
|||||||
Reference in New Issue
Block a user