From d30c66d15e6720bd8f32a162135765a595bdb1ed Mon Sep 17 00:00:00 2001 From: Matthias Wilke Date: Mon, 11 Sep 2023 21:21:26 +0200 Subject: [PATCH] Fix SFXPlaybacks. --- .../DefaultUncaughtExceptionHandler.java | 59 ++++---- .../litiengine/sound/MusicPlayback.java | 4 +- .../litiengine/sound/SFXPlayback.java | 18 +-- .../litiengine/sound/SoundEngine.java | 26 ++-- .../litiengine/sound/SoundPlayback.java | 132 ++++++++---------- 5 files changed, 108 insertions(+), 131 deletions(-) diff --git a/litiengine/src/main/java/de/gurkenlabs/litiengine/DefaultUncaughtExceptionHandler.java b/litiengine/src/main/java/de/gurkenlabs/litiengine/DefaultUncaughtExceptionHandler.java index 221bf34ec..c259dd3b0 100644 --- a/litiengine/src/main/java/de/gurkenlabs/litiengine/DefaultUncaughtExceptionHandler.java +++ b/litiengine/src/main/java/de/gurkenlabs/litiengine/DefaultUncaughtExceptionHandler.java @@ -1,5 +1,8 @@ package de.gurkenlabs.litiengine; +import static de.gurkenlabs.litiengine.util.io.FileUtilities.humanReadableByteCount; + +import de.gurkenlabs.litiengine.configuration.ClientConfiguration; import java.awt.DisplayMode; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; @@ -14,24 +17,23 @@ import java.util.logging.Level; import java.util.logging.Logger; -import de.gurkenlabs.litiengine.configuration.ClientConfiguration; - -import static de.gurkenlabs.litiengine.util.io.FileUtilities.humanReadableByteCount; - /** - * Handles the uncaught exceptions that might occur while running a game or application with the LITIENGINE. + * Handles the uncaught exceptions that might occur while running a game or application with the + * LITIENGINE. *

- * It provides proper logging of the exception in a {@code crash.txt} file in the game's root directory that can be - * further used to report the issue if it's a generic one. + * It provides proper logging of the exception in a {@code crash.txt} file in the game's root + * directory that can be further used to report the issue if it's a generic one. *

*

- * Depending on the configuration, the default behavior might force the game to exit upon an unexpected exception which - * can be useful to detect problems in your game early. + * Depending on the configuration, the default behavior might force the game to exit upon an + * unexpected exception which can be useful to detect problems in your game early. * * @see ClientConfiguration#exitOnError() */ public class DefaultUncaughtExceptionHandler implements UncaughtExceptionHandler { - private static final Logger log = Logger.getLogger(DefaultUncaughtExceptionHandler.class.getName()); + + private static final Logger log = Logger.getLogger( + DefaultUncaughtExceptionHandler.class.getName()); private volatile boolean exitOnException; private volatile boolean dumpThreads; @@ -39,9 +41,8 @@ public class DefaultUncaughtExceptionHandler implements UncaughtExceptionHandler /** * Initializes a new instance of the {@code DefaultUncaughtExceptionHandler} class. * - * @param exitOnException - * A flag indicating whether the game should exit when an unexpected exception occurs. The game will still exit - * if it encounters an Error. + * @param exitOnException A flag indicating whether the game should exit when an unexpected + * exception occurs. The game will still exit if it encounters an Error. */ public DefaultUncaughtExceptionHandler(boolean exitOnException) { this(exitOnException, false); @@ -50,11 +51,10 @@ public DefaultUncaughtExceptionHandler(boolean exitOnException) { /** * Initializes a new instance of the {@code DefaultUncaughtExceptionHandler} class. * - * @param exitOnException - * A flag indicating whether the game should exit when an unexpected exception occurs. The game will still exit - * if it encounters an Error - * @param dumpThreads - * A flag indicating whether the crash report should contain an additional thread dump. + * @param exitOnException A flag indicating whether the game should exit when an unexpected + * exception occurs. The game will still exit if it encounters an Error + * @param dumpThreads A flag indicating whether the crash report should contain an additional + * thread dump. */ public DefaultUncaughtExceptionHandler(boolean exitOnException, boolean dumpThreads) { this.exitOnException = exitOnException; @@ -63,9 +63,6 @@ public DefaultUncaughtExceptionHandler(boolean exitOnException, boolean dumpThre @Override public void uncaughtException(final Thread t, final Throwable e) { - if (e instanceof ThreadDeath) - return; - try (PrintStream stream = new PrintStream("crash.txt")) { stream.print(new Date() + " "); stream.println(t.getName() + " threw an exception:"); @@ -107,8 +104,7 @@ public boolean dumpsThreads() { /** * Set whether the game will exit upon an unhandled exception. * - * @param exit - * The flag that defines whether the game will exit upon an unhandled exception. + * @param exit The flag that defines whether the game will exit upon an unhandled exception. */ public void setExitOnException(boolean exit) { this.exitOnException = exit; @@ -117,8 +113,7 @@ public void setExitOnException(boolean exit) { /** * Set whether the generated crash report will contain an additional thread dump * - * @param dumpThreads - * The flag that defines whether crash report will contain a thread dump. + * @param dumpThreads The flag that defines whether crash report will contain a thread dump. */ public void dumpThreads(boolean dumpThreads) { this.dumpThreads = dumpThreads; @@ -161,19 +156,23 @@ protected static String getSystemInfo() { long freeHeapSize = Runtime.getRuntime().freeMemory(); text.append("\tMax heap size: ").append(humanReadableByteCount(maxHeapSize)).append("\n"); text.append("\tCurrent heap size: ").append(humanReadableByteCount(heapSize)).append("\n"); - text.append("\tHeap used: ").append(humanReadableByteCount(heapSize - freeHeapSize)).append("\n"); + text.append("\tHeap used: ").append(humanReadableByteCount(heapSize - freeHeapSize)) + .append("\n"); text.append("\tFree heap: ").append(humanReadableByteCount(freeHeapSize)).append("\n"); - text.append("Java Version: ").append(System.getProperty("java.runtime.name")).append(" ").append(System.getProperty("java.runtime.version")) - .append(" \n"); + text.append("Java Version: ").append(System.getProperty("java.runtime.name")).append(" ") + .append(System.getProperty("java.runtime.version")) + .append(" \n"); text.append("\tVendor: ").append(System.getProperty("java.vm.vendor")).append("\n"); - text.append("Uptime: ").append(Duration.ofMillis(ManagementFactory.getRuntimeMXBean().getUptime())).append("\n"); + text.append("Uptime: ") + .append(Duration.ofMillis(ManagementFactory.getRuntimeMXBean().getUptime())).append("\n"); GraphicsEnvironment g = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice[] screens = g.getScreenDevices(); text.append("Screens: ").append(screens.length).append("\n"); for (int i = 0; i < screens.length; i++) { GraphicsDevice screen = screens[i]; DisplayMode displayMode = screen.getDisplayMode(); - text.append("\tScreen ").append(i).append(": ").append(displayMode.getWidth()).append("x").append(displayMode.getHeight()); + text.append("\tScreen ").append(i).append(": ").append(displayMode.getWidth()).append("x") + .append(displayMode.getHeight()); if (displayMode.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN) { text.append("@").append(displayMode.getRefreshRate()).append("hz"); } diff --git a/litiengine/src/main/java/de/gurkenlabs/litiengine/sound/MusicPlayback.java b/litiengine/src/main/java/de/gurkenlabs/litiengine/sound/MusicPlayback.java index b45c0619f..581c52869 100644 --- a/litiengine/src/main/java/de/gurkenlabs/litiengine/sound/MusicPlayback.java +++ b/litiengine/src/main/java/de/gurkenlabs/litiengine/sound/MusicPlayback.java @@ -5,8 +5,8 @@ /** A {@code SoundPlayback} implementation for the playback music. */ public class MusicPlayback extends SoundPlayback { - private Track track; - private VolumeControl musicVolume; + private final Track track; + private final VolumeControl musicVolume; MusicPlayback(Track track) throws LineUnavailableException { super(track.getFormat()); diff --git a/litiengine/src/main/java/de/gurkenlabs/litiengine/sound/SFXPlayback.java b/litiengine/src/main/java/de/gurkenlabs/litiengine/sound/SFXPlayback.java index 05fea160b..c6f06ba85 100644 --- a/litiengine/src/main/java/de/gurkenlabs/litiengine/sound/SFXPlayback.java +++ b/litiengine/src/main/java/de/gurkenlabs/litiengine/sound/SFXPlayback.java @@ -8,13 +8,13 @@ /** A {@code SoundPlayback} implementation for the playback of sound effects. */ public class SFXPlayback extends SoundPlayback { - private Sound sound; - private FloatControl panControl; - private Supplier source; - private int range; - private float volumeModifier; - private VolumeControl volume; - private boolean loop; + private final Sound sound; + private final FloatControl panControl; + private final Supplier source; + private final int range; + private final float volumeModifier; + private final VolumeControl volume; + private final boolean loop; SFXPlayback(Sound sound, Supplier source, boolean loop, int range, float volumeModifier) throws LineUnavailableException { @@ -39,8 +39,8 @@ public void run() { return; } } while (this.loop); - } catch (Throwable t) { - t.printStackTrace(); + } catch (LineUnavailableException e) { + e.printStackTrace(); } finally { this.finish(); } diff --git a/litiengine/src/main/java/de/gurkenlabs/litiengine/sound/SoundEngine.java b/litiengine/src/main/java/de/gurkenlabs/litiengine/sound/SoundEngine.java index 9e60586fd..eb72babd7 100644 --- a/litiengine/src/main/java/de/gurkenlabs/litiengine/sound/SoundEngine.java +++ b/litiengine/src/main/java/de/gurkenlabs/litiengine/sound/SoundEngine.java @@ -148,7 +148,8 @@ public synchronized MusicPlayback playMusic( return music; } - try (MusicPlayback playback = new MusicPlayback(track)) { + try { + MusicPlayback playback = new MusicPlayback(track); if (config != null) { config.accept(playback); } @@ -601,23 +602,21 @@ public void update() { Iterator iter = sounds.iterator(); while (iter.hasNext()) { - try (SFXPlayback s = iter.next()) { - if (s.isPlaying()) { - s.updateLocation(listenerLocation); - } else { - iter.remove(); - } + SFXPlayback s = iter.next(); + if (s.isPlaying()) { + s.updateLocation(listenerLocation); + } else { + iter.remove(); } } Iterator iter2 = allMusic.iterator(); while (iter.hasNext()) { - try (MusicPlayback s = iter2.next()) { - if (s.isPlaying()) { - s.setMusicVolume(Game.config().sound().getMusicVolume()); - } else { - iter.remove(); - } + MusicPlayback s = iter2.next(); + if (s.isPlaying()) { + s.setMusicVolume(Game.config().sound().getMusicVolume()); + } else { + iter.remove(); } } @@ -651,6 +650,7 @@ private SFXPlayback playSound( return null; } playback.start(); + System.out.println("Played sound: " + sound.getName()); return playback; } diff --git a/litiengine/src/main/java/de/gurkenlabs/litiengine/sound/SoundPlayback.java b/litiengine/src/main/java/de/gurkenlabs/litiengine/sound/SoundPlayback.java index 9073842db..4777bac74 100644 --- a/litiengine/src/main/java/de/gurkenlabs/litiengine/sound/SoundPlayback.java +++ b/litiengine/src/main/java/de/gurkenlabs/litiengine/sound/SoundPlayback.java @@ -17,15 +17,16 @@ import javax.sound.sampled.SourceDataLine; /** - * The {@code SoundPlayback} class is a wrapper {@code SourceDataLine} on which a {@code Sound} playback can be carried - * out. + * The {@code SoundPlayback} class is a wrapper {@code SourceDataLine} on which a {@code Sound} + * playback can be carried out. * * @see #play(Sound) */ -public abstract class SoundPlayback implements Runnable, AutoCloseable { +public abstract class SoundPlayback implements Runnable { + protected final SourceDataLine line; - private FloatControl gainControl; - private BooleanControl muteControl; + private final FloatControl gainControl; + private final BooleanControl muteControl; private boolean started = false; private volatile boolean cancelled = false; @@ -33,9 +34,9 @@ public abstract class SoundPlayback implements Runnable, AutoCloseable { private final Collection listeners = ConcurrentHashMap.newKeySet(); private final Collection volumeControls = - Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); - private VolumeControl masterVolume; - private AtomicInteger miscVolume = new AtomicInteger(0x3f800000); // floatToIntBits(1f) + Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); + private final VolumeControl masterVolume; + private final AtomicInteger miscVolume = new AtomicInteger(0x3f800000); // floatToIntBits(1f) SoundPlayback(AudioFormat format) throws LineUnavailableException { // acquire resources in the constructor so that they can be used before the task is started @@ -50,8 +51,7 @@ public abstract class SoundPlayback implements Runnable, AutoCloseable { /** * Starts playing the audio. * - * @throws IllegalStateException - * if the audio has already been started + * @throws IllegalStateException if the audio has already been started */ public synchronized void start() { if (this.started) { @@ -64,8 +64,7 @@ public synchronized void start() { /** * Adds a {@code SoundPlaybackListener} to this instance. * - * @param listener - * The {@code SoundPlaybackListener} to be added. + * @param listener The {@code SoundPlaybackListener} to be added. */ public void addSoundPlaybackListener(SoundPlaybackListener listener) { this.listeners.add(listener); @@ -74,8 +73,7 @@ public void addSoundPlaybackListener(SoundPlaybackListener listener) { /** * Removes a {@code SoundPlaybackListener} from this instance. * - * @param listener - * The {@code SoundPlaybackListener} to be removed. + * @param listener The {@code SoundPlaybackListener} to be removed. */ public void removeSoundPlaybackListener(SoundPlaybackListener listener) { this.listeners.remove(listener); @@ -84,8 +82,7 @@ public void removeSoundPlaybackListener(SoundPlaybackListener listener) { /** * Sets the paused state of this playback to the provided value. * - * @param paused - * Whether to pause or resume this playback + * @param paused Whether to pause or resume this playback */ public void setPaused(boolean paused) { if (paused) { @@ -95,14 +92,18 @@ public void setPaused(boolean paused) { } } - /** Pauses this playback. If this playback is already paused, this call has no effect. */ + /** + * Pauses this playback. If this playback is already paused, this call has no effect. + */ public void pausePlayback() { if (this.line.isOpen()) { this.line.stop(); } } - /** Resumes this playback. If this playback is already playing, this call has no effect. */ + /** + * Resumes this playback. If this playback is already playing, this call has no effect. + */ public void resumePlayback() { if (this.line.isOpen()) { this.line.start(); @@ -112,23 +113,19 @@ public void resumePlayback() { /** * Fades this playback's volume to 0 over the given duration. * - * @param duration - * the fade duration in milliseconds. + * @param duration the fade duration in milliseconds. */ public void fade(int duration) { this.fade(duration, 0f, TweenFunction.LINEAR); } /** - * Fades this playback's volume to the target value over the given duration using the given {@code - * TweenFunction}. + * Fades this playback's volume to the target value over the given duration using the given + * {@code TweenFunction}. * - * @param duration - * the fade duration in milliseconds. - * @param target - * the target volume at the end of the fade - * @param easingType - * the TweenFunction determining the falloff curve of this fade. + * @param duration the fade duration in milliseconds. + * @param target the target volume at the end of the fade + * @param easingType the TweenFunction determining the falloff curve of this fade. */ public void fade(int duration, float target, TweenFunction easingType) { for (VolumeControl v : this.getVolumeControls()) { @@ -146,8 +143,9 @@ public boolean isPaused() { } /** - * Determines if this playback has sound to play. If it is paused but still in the middle of playback, it will return - * {@code true}, but it will return {@code false} if it has finished or it has been cancelled. + * Determines if this playback has sound to play. If it is paused but still in the middle of + * playback, it will return {@code true}, but it will return {@code false} if it has finished or + * it has been cancelled. * * @return Whether this playback has sound to play */ @@ -156,7 +154,8 @@ public boolean isPlaying() { } /** - * Attempts to cancel the playback of this audio. If the playback was successfully cancelled, it will notify listeners. + * Attempts to cancel the playback of this audio. If the playback was successfully cancelled, it + * will notify listeners. */ public synchronized void cancel() { if (!this.started) { @@ -174,7 +173,8 @@ public synchronized void cancel() { } /** - * Gets the current volume of this playback, considering all {@code VolumeControl} objects created for it. + * Gets the current volume of this playback, considering all {@code VolumeControl} objects created + * for it. * * @return The current volume. */ @@ -186,8 +186,8 @@ public float getMasterVolume() { } /** - * Gets the current master volume of this playback. This will be approximately equal to the value set by a previous call - * to {@code setVolume}, though rounding errors may occur. + * Gets the current master volume of this playback. This will be approximately equal to the value + * set by a previous call to {@code setVolume}, though rounding errors may occur. * * @return The settable volume. */ @@ -198,8 +198,7 @@ public float getVolume() { /** * Sets the master volume of this playback. * - * @param volume - * The new volume. + * @param volume The new volume. */ public void setVolume(float volume) { this.masterVolume.set(volume); @@ -222,8 +221,7 @@ void play() { /** * Plays a sound to this object's data line. * - * @param sound - * The sound to play + * @param sound The sound to play * @return Whether the sound was cancelled while playing */ boolean play(Sound sound) throws LineUnavailableException { @@ -234,7 +232,8 @@ boolean play(Sound sound) throws LineUnavailableException { // math hacks here: we're getting just over half the buffer size, but it needs to be an integral // number of sample frames len = (this.line.getBufferSize() / len / 2 + 1) * len; - for (int i = 0; i < data.length; i += this.line.write(data, i, Math.min(len, data.length - i))) { + for (int i = 0; i < data.length; + i += this.line.write(data, i, Math.min(len, data.length - i))) { if (this.cancelled || !line.isOpen()) { return true; } @@ -243,12 +242,13 @@ boolean play(Sound sound) throws LineUnavailableException { } /** - * Finishes the playback. If this playback was not cancelled in the process, it will notify listeners. + * Finishes the playback. If this playback was not cancelled in the process, it will notify + * listeners. */ void finish() { this.line.drain(); synchronized (this) { - close(); + cancel(); if (!this.cancelled) { SoundEvent event = new SoundEvent(this, null); for (SoundPlaybackListener listener : this.listeners) { @@ -274,33 +274,19 @@ void updateVolume() { } } - /** - * Use {@link #cancel()} - */ - @Deprecated - public void close() { - if (this.line != null && this.line.isOpen()) { - try { - this.line.close(); - } catch (Throwable t) { - t.printStackTrace(); // not much else we can do - } - } - for (VolumeControl vc : volumeControls) { - vc.close(); - } - } /** - * An object for controlling the volume of a {@code SoundPlayback}. Each distinct instance represents an independent - * factor contributing to its volume. + * An object for controlling the volume of a {@code SoundPlayback}. Each distinct instance + * represents an independent factor contributing to its volume. * * @see SoundPlayback#createVolumeControl() */ public class VolumeControl implements Tweenable, AutoCloseable { + private volatile float value = 1f; - private VolumeControl() {} + private VolumeControl() { + } /** * Gets the value of this volume control. @@ -314,8 +300,7 @@ public float get() { /** * Sets the value of this volume control. * - * @param value - * The value to be set. + * @param value The value to be set. */ public void set(float value) { if (value < 0f) { @@ -326,33 +311,26 @@ public void set(float value) { } @Override - @Deprecated public void close() { // clean up the instance without affecting the volume SoundPlayback.this.miscVolume.accumulateAndGet( - Float.floatToRawIntBits(this.value), - (a, b) -> Float.floatToRawIntBits(Float.intBitsToFloat(a) * Float.intBitsToFloat(b))); + Float.floatToRawIntBits(this.value), + (a, b) -> Float.floatToRawIntBits(Float.intBitsToFloat(a) * Float.intBitsToFloat(b))); } @Override public float[] getTweenValues(TweenType tweenType) { - switch (tweenType) { - case VOLUME: - return new float[] {(float) this.get()}; - default: - return Tweenable.super.getTweenValues(tweenType); - } + return switch (tweenType) { + case VOLUME -> new float[]{this.get()}; + default -> Tweenable.super.getTweenValues(tweenType); + }; } @Override public void setTweenValues(TweenType tweenType, float[] newValues) { switch (tweenType) { - case VOLUME: - this.set(newValues[0]); - break; - default: - Tweenable.super.setTweenValues(tweenType, newValues); - break; + case VOLUME -> this.set(newValues[0]); + default -> Tweenable.super.setTweenValues(tweenType, newValues); } } }