Skip to content

Commit

Permalink
Reset all client state when disconnected
Browse files Browse the repository at this point in the history
  • Loading branch information
Barabas5532 committed Mar 24, 2024
1 parent 11ef812 commit c21ee5b
Show file tree
Hide file tree
Showing 16 changed files with 437 additions and 358 deletions.
24 changes: 13 additions & 11 deletions frontend/lib/api/api_websocket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,22 @@ sealed class ApiMessage with _$ApiMessage {
}

@riverpod
ApiWebsocket apiWebsocket(ApiWebsocketRef ref) {
return ApiWebsocket(
websocket: ref.watch(
robustWebsocketProvider(
Uri.parse(kShrapnelUri),
ApiWebsocket? apiWebsocket(ApiWebsocketRef ref) {
final isAlive = ref.watch(isAliveProvider);
if (isAlive) {
return ApiWebsocket(
websocket: ref.watch(
robustWebsocketProvider(
Uri.parse(kShrapnelUri),
),
),
),
);
);
}

return null;
}

// TODO make this implement MessageTransport<ApiMessage, ApiMessage>
class ApiWebsocket {
ApiWebsocket({required RobustWebsocket websocket}) : _websocket = websocket;

Expand All @@ -92,10 +98,6 @@ class ApiWebsocket {
(event) => 'received: $event',
);

late final Stream<void> connectionStream = _websocket.connectionStream;

bool get isAlive => _websocket.isAlive;

void send(ApiMessage message) {
_log.finest('sending: $message');
_websocket.sendMessage(message.toProto().writeToBuffer());
Expand Down
19 changes: 12 additions & 7 deletions frontend/lib/audio_events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,18 @@ sealed class AudioEventMessage with _$AudioEventMessage {
}

final audioClippingServiceProvider = AutoDisposeChangeNotifierProvider(
(ref) => AudioClippingService(
stream: ref
.watch(apiWebsocketProvider)
.stream
.whereType<ApiMessageAudioEvent>()
.map((event) => event.message),
),
(ref) {
final websocket = ref.watch(apiWebsocketProvider);
if (websocket != null) {
return AudioClippingService(
stream: websocket.stream
.whereType<ApiMessageAudioEvent>()
.map((event) => event.message),
);
}

return null;
},
);

class AudioClippingService extends ChangeNotifier {
Expand Down
6 changes: 0 additions & 6 deletions frontend/lib/core/message_transport.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@ abstract class MessageTransport<T1, T2> {
/// Messages from the other side of the connection will appear in this stream.
Stream<T2> get stream;

/// A null is emitted every time a connection is successfully created
Stream<void> get connectionStream;

/// Returns true if the connection is alive at the moment
bool get isAlive;

/// Must be called to clean up resource after the transport is no longer in
/// use.
void dispose();
Expand Down
24 changes: 17 additions & 7 deletions frontend/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,10 @@ class MyHomePage extends riverpod.ConsumerWidget {
child: IconButton(
icon: const Icon(Icons.menu_book_outlined),
key: const Key('midi-learn-button'),
onPressed: () {
ref.read(midiLearnServiceProvider.notifier).startLearning();
},
onPressed: ref.watch(midiLearnServiceProvider).maybeWhen(
loading: null,
orElse: () => () => startMidiLearning(ref),
),
),
),
Tooltip(
Expand Down Expand Up @@ -277,17 +278,22 @@ class MyHomePage extends riverpod.ConsumerWidget {
message: 'Input clipping',
child: Icon(
Icons.input,
color: ref.watch(audioClippingServiceProvider).inputIsClipped
? Colors.red
: null,
color:
ref.watch(audioClippingServiceProvider)?.inputIsClipped ??
false
? Colors.red
: null,
),
),
const SizedBox(width: 8),
Tooltip(
message: 'Output clipping',
child: Icon(
Icons.output,
color: ref.watch(audioClippingServiceProvider).outputIsClipped
color: ref
.watch(audioClippingServiceProvider)
?.outputIsClipped ??
false
? Colors.red
: null,
),
Expand All @@ -303,6 +309,10 @@ class MyHomePage extends riverpod.ConsumerWidget {
),
);
}

void startMidiLearning(riverpod.WidgetRef ref) {
ref.read(midiLearnServiceProvider.notifier).startLearning();
}
}

class ProvisioningPage extends StatelessWidget {
Expand Down
67 changes: 53 additions & 14 deletions frontend/lib/midi_mapping/model/midi_learn.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,58 @@ import 'service.dart';
final _log = Logger('shrapnel.midi_mapping.model.midi_learn');

final midiLearnServiceProvider =
AutoDisposeStateNotifierProvider<MidiLearnService, MidiLearnState>(
(ref) => MidiLearnService(
mappingService: ref.watch(midiMappingServiceProvider),
parameterUpdates:
ref.watch(parameterServiceProvider).parameterUpdates.map((e) => e.id),
midiMessages: ref
.watch(apiWebsocketProvider)
.stream
.whereType<ApiMessageMidiMapping>()
.map((event) => event.message)
.whereType<MidiMessageReceived>()
.map((event) => event.message),
),
AutoDisposeStateNotifierProvider<MidiLearnServiceBase, MidiLearnState>(
(ref) {
return switch ((
ref.watch(midiMappingServiceProvider),
ref.watch(parameterServiceProvider),
ref.watch(apiWebsocketProvider),
)) {
(final midiMappingService?, final parameterService?, final websocket?) =>
MidiLearnService(
mappingService: midiMappingService,
parameterUpdates: parameterService.parameterUpdates.map((e) => e.id),
midiMessages: websocket.stream
.whereType<ApiMessageMidiMapping>()
.map((event) => event.message)
.whereType<MidiMessageReceived>()
.map((event) => event.message),
),
_ => MidiLearnServiceLoading(),
};
},
);

class MidiLearnService extends StateNotifier<MidiLearnState> {
abstract class MidiLearnServiceBase extends StateNotifier<MidiLearnState> {
MidiLearnServiceBase(super._state);

void startLearning();

void cancelLearning();

Future<void> undoRemoveSimilarMappings();
}

class MidiLearnServiceLoading extends MidiLearnServiceBase {
MidiLearnServiceLoading() : super(const MidiLearnState.loading());

@override
void cancelLearning() {
throw StateError("Can't cancel learning while loading");
}

@override
void startLearning() {
throw StateError("Can't start learning while loading");
}

@override
Future<void> undoRemoveSimilarMappings() {
throw StateError("Can't undo learning while loading");
}
}

class MidiLearnService extends MidiLearnServiceBase {
MidiLearnService({
required this.mappingService,
required this.parameterUpdates,
Expand All @@ -61,6 +97,7 @@ class MidiLearnService extends StateNotifier<MidiLearnState> {
final Stream<String> parameterUpdates;
final Stream<MidiMessage> midiMessages;

@override
void startLearning() {
state.maybeWhen(
idle: (_) => state = const MidiLearnState.waitForParameter(),
Expand Down Expand Up @@ -136,10 +173,12 @@ class MidiLearnService extends StateNotifier<MidiLearnState> {
);
}

@override
void cancelLearning() {
state = const MidiLearnState.idle(null);
}

@override
Future<void> undoRemoveSimilarMappings() async {
unawaited(
state.maybeWhen(
Expand Down
5 changes: 5 additions & 0 deletions frontend/lib/midi_mapping/model/midi_learn_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,15 @@ part 'midi_learn_state.freezed.dart';

@freezed
class MidiLearnState with _$MidiLearnState {
const factory MidiLearnState.loading() = _Loading;

const factory MidiLearnState.idle(
List<MapEntry<MidiMappingId, MidiMapping>>? duplicates,
) = _Idle;

const factory MidiLearnState.waitForParameter() = _WaitForParameter;

const factory MidiLearnState.waitForMidi(String id) = _WaitForMidi;

const factory MidiLearnState.savingMapping() = _SavingMapping;
}
33 changes: 15 additions & 18 deletions frontend/lib/midi_mapping/model/service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,23 @@ import '../model/models.dart';
final _log = Logger('midi_mapping_service');

final midiMappingTransportProvider = AutoDisposeProvider(
(ref) => MidiMappingTransport(
websocket: ref.watch(apiWebsocketProvider),
),
(ref) {
return switch (ref.watch(apiWebsocketProvider)) {
final websocket? => MidiMappingTransport(websocket: websocket),
null => null,
};
},
);

final midiMappingServiceProvider = AutoDisposeChangeNotifierProvider(
(ref) => MidiMappingService(
websocket: ref.watch(midiMappingTransportProvider),
),
(ref) {
return switch (ref.watch(midiMappingTransportProvider)) {
final transport? => MidiMappingService(
websocket: transport,
),
null => null,
};
},
);

class MidiMappingTransport
Expand All @@ -68,32 +76,21 @@ class MidiMappingTransport
(event) => 'receive message: $event',
);

@override
Stream<void> get connectionStream => websocket.connectionStream;

ApiWebsocket websocket;

@override
void dispose() {
unawaited(_controller.close());
}

@override
bool get isAlive => websocket.isAlive;
}

class MidiMappingService extends ChangeNotifier {
MidiMappingService({
required this.websocket,
}) {
_mappingsView = UnmodifiableMapView(__mappings);

websocket.connectionStream.listen((_) async => getMapping());
_subscription = websocket.stream.listen(_handleMessage);

if (websocket.isAlive) {
unawaited(getMapping());
}
unawaited(getMapping());
}

static const responseTimeout = Duration(milliseconds: 500);
Expand Down
2 changes: 2 additions & 0 deletions frontend/lib/midi_mapping/view/midi_learn.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ class MidiLearnStatus extends ConsumerWidget {
final midiLearnService = ref.read(midiLearnServiceProvider.notifier);

final isInProgress = midiLearnState.when(
loading: () => false,
idle: (_) => false,
waitForParameter: () => true,
waitForMidi: (_) => true,
savingMapping: () => true,
);

final isCancellable = midiLearnState.when(
loading: () => false,
idle: (_) => false,
waitForParameter: () => true,
waitForMidi: (_) => true,
Expand Down
Loading

0 comments on commit c21ee5b

Please sign in to comment.