Skip to content

Commit

Permalink
Merge pull request #69 from alnitak/max-voices-reset
Browse files Browse the repository at this point in the history
fix: crash on deinit() #68 and parameters retention after deinit()
  • Loading branch information
alnitak committed Mar 26, 2024
2 parents decd414 + 1e841d7 commit 693dc25
Show file tree
Hide file tree
Showing 10 changed files with 321 additions and 218 deletions.
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ packages:
path: ".."
relative: true
source: path
version: "2.0.0-pre.3"
version: "2.0.0-pre.4"
flutter_test:
dependency: "direct dev"
description: flutter
Expand Down
110 changes: 65 additions & 45 deletions example/tests/tests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,12 @@ void main() async {

await runZonedGuarded(
() async => testProtectVoice(),
(error, stack) {
stderr.writeln('TEST error: $error\nstack: $stack');
exitCode = 1;
},
(error, stack) => printError,
);

await runZonedGuarded(
() async => testAllInstancesFinished(),
(error, stack) {
stderr.writeln('TEST error: $error\nstack: $stack');
exitCode = 1;
},
(error, stack) => printError,
);

await runZonedGuarded(
Expand All @@ -57,33 +51,34 @@ void main() async {
// This is to be expected in this test.
return;
}
stderr.writeln('TEST error in zone: $error\nstack: $stack');
exitCode = 1;
printError(error, stack);
},
);

await runZonedGuarded(
() async => testCreateNotes(),
() async => testAsynchronousDeinit(),
(error, stack) {
stderr.writeln('TEST error: $error\nstack: $stack');
exitCode = 1;
if (error is SoLoudInitializationStoppedByDeinitException) {
// This is to be expected in this test.
return;
}
printError(error, stack);
},
);

await runZonedGuarded(
() async => testCreateNotes(),
(error, stack) => printError,
);

await runZonedGuarded(
() async => testHandles(),
(error, stack) {
stderr.writeln('TEST error: $error\nstack: $stack');
exitCode = 1;
},
(error, stack) => printError,
);

await runZonedGuarded(
() async => testPlaySeekPause(),
(error, stack) {
stderr.writeln('TEST error: $error\nstack: $stack');
exitCode = 1;
},
(error, stack) => printError,
);

stdout.write('\n\n\n---\n\n\n');
Expand Down Expand Up @@ -112,17 +107,12 @@ void main() async {
String output = '';
AudioSource? currentSound;

Future<void> delay(int ms) async {
await Future.delayed(Duration(milliseconds: ms), () {});
}

/// Test synchronous and asynchronous `initialize()` - `deinit()` within
/// short time delays.
/// Test setMaxActiveVoiceCount, setProtectedVoice and getProtectedVoice
Future<void> testProtectVoice() async {
await initialize();

final previous = SoLoud.instance.getMaxActiveVoiceCount();
SoLoud.instance.setMaxActiveVoiceCount(3);
assert(
SoLoud.instance.getMaxActiveVoiceCount() == 3,
Expand Down Expand Up @@ -164,14 +154,13 @@ Future<void> testProtectVoice() async {

// Reset voice count to normal. Workaround to the issue that maxVoiceCount
// persists between different initializations of the engine.
SoLoud.instance.setMaxActiveVoiceCount(previous);

dispose();
}

/// Test allInstancesFinished stream
Future<void> testAllInstancesFinished() async {
final log = Logger('test5');
final log = Logger('testAllInstancesFinished');
await initialize();

await SoLoud.instance.disposeAllSound();
Expand Down Expand Up @@ -205,30 +194,30 @@ Future<void> testAllInstancesFinished() async {

await SoLoud.instance.play(explosion, volume: 0.2);
final songHandle = await SoLoud.instance.play(song, volume: 0.6);
await Future<void>.delayed(const Duration(milliseconds: 500));
await delay(500);
await SoLoud.instance.play(explosion, volume: 0.3);

// Let the second explosion play for its full duration.
await Future<void>.delayed(const Duration(milliseconds: 4000));
await delay(4000);

await SoLoud.instance.stop(songHandle);
await Future<void>.delayed(const Duration(milliseconds: 1000));
await delay(1000);

assert(explosionDisposed, "Explosion sound wasn't disposed.");
assert(songDisposed, "Song sound wasn't disposed.");

dispose();
}

/// Test synchronous `deinit()`
Future<void> testSynchronousDeinit() async {
final log = Logger('test4');
/// Test asynchronous `init()`-`deinit()`
Future<void> testAsynchronousDeinit() async {
final log = Logger('testAsynchronousDeinit');

/// test synchronous init-deinit looping with a short decreasing time
for (var t = 100; t >= 0; t -= 5) {
/// Initialize the player
/// test asynchronous init-deinit looping with a short decreasing time
for (var t = 100; t >= 0; t--) {
var error = '';

/// Initialize the player
unawaited(
SoLoud.instance.init().then(
(_) {},
Expand All @@ -247,7 +236,7 @@ Future<void> testSynchronousDeinit() async {
assert(error.isEmpty, error);

/// wait for [t] ms and deinit()
await Future.delayed(Duration(milliseconds: t), () {});
await delay(t);
SoLoud.instance.deinit();
final after = SoLoudController().soLoudFFI.isInited();

Expand All @@ -258,24 +247,46 @@ Future<void> testSynchronousDeinit() async {

stderr.writeln('------------- awaited init delay $t passed\n');
}
}

/// Test synchronous `init()`-`deinit()`
Future<void> testSynchronousDeinit() async {
final log = Logger('testSynchronousDeinit');

/// test asynchronous init-deinit looping with a short decreasing time without
/// test synchronous init-deinit looping with a short decreasing time
/// waiting for `initialize()` to finish
for (var t = 50; t >= 0; t -= 2) {
for (var t = 100; t >= 0; t--) {
var error = '';

/// Initialize the player
unawaited(SoLoud.instance.init());
await SoLoud.instance.init().then(
(_) {},
onError: (Object e) {
if (e is SoLoudInitializationStoppedByDeinitException) {
// This is to be expected.
log.info('$e');
return;
}
e = 'TEST FAILED delay: $t. Player starting error: $e';
error = e.toString();
},
);
assert(
error.isEmpty,
'ASSERT FAILED delay: $t. The player has not been '
'inited correctly!',
);

/// wait for [t] ms and deinit()
await Future.delayed(Duration(milliseconds: t), () {});
SoLoud.instance.deinit();

assert(
!SoLoudController().soLoudFFI.isInited(),
!SoLoud.instance.isInitialized ||
!SoLoudController().soLoudFFI.isInited(),
'ASSERT FAILED delay: $t. The player has not been '
'inited or deinited correctly!',
);

stderr.writeln('------------- unawaited init delay $t passed\n');
stderr.writeln('------------- awaited init #$t passed\n');
}

/// Try init-play-deinit and again init-play without disposing the sound
Expand Down Expand Up @@ -446,6 +457,10 @@ void dispose() {
SoLoud.instance.deinit();
}

Future<void> delay(int ms) async {
await Future.delayed(Duration(milliseconds: ms), () {});
}

Future<void> loadAsset() async {
if (currentSound != null) {
await SoLoud.instance.disposeSound(currentSound!);
Expand All @@ -461,3 +476,8 @@ Future<void> loadAsset() async {
}
});
}

void printError(Object error, StackTrace stack) {
stderr.writeln('TEST error: $error\nstack: $stack');
exitCode = 1;
}
7 changes: 6 additions & 1 deletion lib/src/enums.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ enum PlayerErrors {
maxNumberOfFiltersReached(14),

/// The filter has already been added.
filterAlreadyAdded(15);
filterAlreadyAdded(15),

/// Player already inited.
playerAlreadyInited(16);

const PlayerErrors(this.value);

Expand Down Expand Up @@ -147,6 +150,8 @@ enum PlayerErrors {
return 'The maximum number of filters has been reached (default is 8)!';
case PlayerErrors.filterAlreadyAdded:
return 'Filter not found!';
case PlayerErrors.playerAlreadyInited:
return 'The player has already been inited!';
}
}

Expand Down
2 changes: 2 additions & 0 deletions lib/src/exceptions/exceptions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ abstract class SoLoudCppException extends SoLoudException {
return const SoLoudMaxFilterNumberReachedException();
case PlayerErrors.filterAlreadyAdded:
return const SoLoudFilterAlreadyAddedException();
case PlayerErrors.playerAlreadyInited:
return const SoLoudPlayerAlreadyInitializedException();
}
}
}
11 changes: 11 additions & 0 deletions lib/src/exceptions/exceptions_from_cpp.dart
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,14 @@ class SoLoudFilterAlreadyAddedException extends SoLoudCppException {
String get description => 'Asking to add a filter that has '
'already been added. Only one of each type is allowed (on the C++ side).';
}

/// An exception that is thrown when SoLoud (C++) cannot add a filter
/// that has already been added.
class SoLoudPlayerAlreadyInitializedException extends SoLoudCppException {
/// Creates a new [SoLoudPlayerAlreadyInitializedException].
const SoLoudPlayerAlreadyInitializedException([super.message]);

@override
String get description => 'The player has already been initialized '
'(on the C++ side).';
}
1 change: 0 additions & 1 deletion lib/src/soloud.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1402,7 +1402,6 @@ interface class SoLoud {
/// required, SoLoud can be modified to support more. But seriously, if you
/// need more than 4095 sounds at once, you're probably going to make
/// some serious changes in any case.
// TODO(filiph): Make sure the set maxVoiceCount _doesn't_ survive deinit
void setMaxActiveVoiceCount(int maxVoiceCount) {
if (!isInitialized) {
throw const SoLoudNotInitializedException();
Expand Down
Loading

0 comments on commit 693dc25

Please sign in to comment.