feat: Implement audio shutdown procedure for graceful app exit
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -26,6 +26,8 @@ class Wolf3d {
|
||||
/// Shared engine audio backend used by menus and gameplay sessions.
|
||||
final EngineAudio audio;
|
||||
|
||||
Future<void>? _audioShutdownFuture;
|
||||
|
||||
/// Engine menu background color as 24-bit RGB.
|
||||
int menuBackgroundRgb = 0x890000;
|
||||
|
||||
@@ -246,6 +248,24 @@ class Wolf3d {
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Stops and disposes shared audio exactly once for app shutdown.
|
||||
///
|
||||
/// Repeated calls return the same in-flight/completed future so hosts can
|
||||
/// safely invoke shutdown from multiple lifecycle paths.
|
||||
Future<void> shutdownAudio() {
|
||||
final existing = _audioShutdownFuture;
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
final shutdown = () async {
|
||||
await audio.stopAllAudio();
|
||||
audio.dispose();
|
||||
}();
|
||||
_audioShutdownFuture = shutdown;
|
||||
return shutdown;
|
||||
}
|
||||
|
||||
/// Loads an asset from the Flutter bundle, returning `null` when absent.
|
||||
Future<ByteData?> _tryLoad(String path) async {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
import 'package:wolf_3d_flutter/wolf_3d_flutter.dart';
|
||||
|
||||
class _CountingAudio implements EngineAudio {
|
||||
@override
|
||||
WolfensteinData? activeGame;
|
||||
|
||||
int stopAllAudioCallCount = 0;
|
||||
int disposeCallCount = 0;
|
||||
|
||||
@override
|
||||
Future<void> debugSoundTest() async {}
|
||||
|
||||
@override
|
||||
Future<void> init() async {}
|
||||
|
||||
@override
|
||||
void playLevelMusic(Music music) {}
|
||||
|
||||
@override
|
||||
void playMenuMusic() {}
|
||||
|
||||
@override
|
||||
void playSoundEffect(SoundEffect effect) {}
|
||||
|
||||
@override
|
||||
void playSoundEffectId(int sfxId) {}
|
||||
|
||||
@override
|
||||
Future<void> stopAllAudio() async {
|
||||
stopAllAudioCallCount++;
|
||||
await Future<void>.delayed(const Duration(milliseconds: 1));
|
||||
}
|
||||
|
||||
@override
|
||||
void stopMusic() {}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
disposeCallCount++;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Wolf3d.shutdownAudio', () {
|
||||
test('stops and disposes audio once', () async {
|
||||
final audio = _CountingAudio();
|
||||
final wolf3d = Wolf3d(audioBackend: audio);
|
||||
|
||||
await wolf3d.shutdownAudio();
|
||||
await wolf3d.shutdownAudio();
|
||||
|
||||
expect(audio.stopAllAudioCallCount, 1);
|
||||
expect(audio.disposeCallCount, 1);
|
||||
});
|
||||
|
||||
test('concurrent calls share the same shutdown work', () async {
|
||||
final audio = _CountingAudio();
|
||||
final wolf3d = Wolf3d(audioBackend: audio);
|
||||
|
||||
await Future.wait<void>([
|
||||
wolf3d.shutdownAudio(),
|
||||
wolf3d.shutdownAudio(),
|
||||
wolf3d.shutdownAudio(),
|
||||
]);
|
||||
|
||||
expect(audio.stopAllAudioCallCount, 1);
|
||||
expect(audio.disposeCallCount, 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user