audio config, refactor CountUpAndDownLatch, allow parents and children

This commit is contained in:
Bixilon 2021-05-24 22:46:43 +02:00
parent 4534304d15
commit ead9f6bfe3
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
16 changed files with 219 additions and 165 deletions

View File

@ -160,11 +160,7 @@ public final class Minosoft {
taskWorker.addTask(new Task((progress) -> StartProgressWindow.show(START_STATUS_LATCH), "Progress Window", "Display progress window", Priorities.HIGH, TaskImportance.OPTIONAL, "JavaFX Toolkit", "Configuration"));
}
taskWorker.work(START_STATUS_LATCH);
try {
START_STATUS_LATCH.waitUntilZero();
} catch (InterruptedException e) {
e.printStackTrace();
}
START_STATUS_LATCH.await();
Log.info("Everything initialized!");
if (StaticConfiguration.HEADLESS_MODE) {
return;

View File

@ -16,6 +16,7 @@ package de.bixilon.minosoft.config.config.game
import de.bixilon.minosoft.config.config.game.controls.ControlsGameConfig
import de.bixilon.minosoft.config.config.game.elements.ElementsGameConfig
import de.bixilon.minosoft.config.config.game.graphics.GraphicsGameConfig
import de.bixilon.minosoft.config.config.game.sound.SoundConfig
data class GameConfig(
var graphics: GraphicsGameConfig = GraphicsGameConfig(),
@ -24,4 +25,5 @@ data class GameConfig(
var controls: ControlsGameConfig = ControlsGameConfig(),
var elements: ElementsGameConfig = ElementsGameConfig(),
var camera: CameraGameConfig = CameraGameConfig(),
var sound: SoundConfig = SoundConfig(),
)

View File

@ -0,0 +1,22 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.config.config.game.sound
import com.squareup.moshi.Json
data class SoundConfig(
var enabled: Boolean = true, // ToDo
@Json(name = "master_volume") var masterVolume: Float = 1.0f,
@Json(name = "enable_packet_sounds") var enablePacketSounds: Boolean = true,
)

View File

@ -140,10 +140,10 @@ class MinecraftAssetsManager(
}
private fun verifyAssets(source: AssetsSource, latch: CountUpAndDownLatch?, assets: Map<ResourceLocation, String>): Map<ResourceLocation, String> {
val assetsLatch = CountUpAndDownLatch(assets.size)
latch?.addCount(assets.size)
val assetsLatch = CountUpAndDownLatch(assets.size, latch)
for (hash in assets.values) {
Minosoft.THREAD_POOL.execute {
// Log.log(LogMessageType.ASSETS, LogLevels.VERBOSE){"Assets, total=${assets.size}, latchTotal=${assetsLatch.total}, current=${assetsLatch.count}"}
val compressed = source != AssetsSource.PIXLYZER
if (StaticConfiguration.DEBUG_SLOW_LOADING) {
Thread.sleep(100L)
@ -151,11 +151,11 @@ class MinecraftAssetsManager(
if (!verifyAssetHash(hash, compressed = compressed)) {
downloadAsset(source, hash)
}
latch?.countDown()
assetsLatch.countDown()
assetsLatch.dec()
}
}
assetsLatch.waitUntilZero()
assetsLatch.awaitWithChange()
return assets
}

View File

@ -36,7 +36,6 @@ data class Version(
val s2CPacketMapping: Map<ConnectionStates, HashBiMap<S2C, Int>>,
) {
var isLoaded = false
var isGettingLoaded = false
val registries: Registries = Registries()
lateinit var assetsManager: MinecraftAssetsManager
lateinit var localeManager: MinecraftLocaleManager
@ -68,9 +67,9 @@ data class Version(
localeManager.load(this, Minosoft.getConfig().config.general.language)
}
@Synchronized
fun load(latch: CountUpAndDownLatch) {
if (isLoaded || isGettingLoaded) {
// already loaded or is getting loaded
if (isLoaded) {
return
}
@ -84,8 +83,7 @@ data class Version(
throw exception
}
}
latch.countUp()
isGettingLoaded = true
latch.inc()
Log.log(LogMessageType.VERSION_LOADING, level = LogLevels.INFO) { "Loading mappings for $this..." }
initializeAssetManger(latch)
val startTime = System.currentTimeMillis()
@ -109,17 +107,16 @@ data class Version(
}
JsonObject()
}
latch.addCount(1)
latch.inc()
registries.load(this, pixlyzerData)
latch.countDown()
latch.dec()
if (pixlyzerData.size() > 0) {
Log.log(LogMessageType.VERSION_LOADING, level = LogLevels.INFO) { "Loaded mappings for $this (${versionName} in ${System.currentTimeMillis() - startTime}ms" }
} else {
Log.log(LogMessageType.VERSION_LOADING, level = LogLevels.WARN) { "Could not load mappings for $this (${versionName}. Some features might not work." }
}
isLoaded = true
isGettingLoaded = false
latch.countDown()
latch.dec()
}
fun unload() {
@ -128,7 +125,6 @@ data class Version(
registries.parentRegistries = null
}
isLoaded = false
isGettingLoaded = false
}
override fun hashCode(): Int {

View File

@ -15,7 +15,6 @@ package de.bixilon.minosoft.gui.main;
import com.jfoenix.controls.JFXAlert;
import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.controls.JFXProgressBar;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.ShutdownReasons;
import de.bixilon.minosoft.data.locale.LocaleManager;
@ -27,6 +26,7 @@ import de.bixilon.minosoft.util.logging.LogMessageType;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.GridPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
@ -36,7 +36,7 @@ import java.util.concurrent.CountDownLatch;
public class StartProgressWindow extends Application {
public static final CountDownLatch TOOLKIT_LATCH = new CountDownLatch(2); // 2 if not started, 1 if started, 0 if loaded
public static JFXAlert<Boolean> progressDialog;
private static JFXProgressBar progressBar;
private static ProgressBar progressBar;
private static Label progressLabel;
private static boolean exit;
@ -56,7 +56,7 @@ public class StartProgressWindow extends Application {
JFXDialogLayout layout = new JFXDialogLayout();
layout.setHeading(new Label(LocaleManager.translate(Strings.MINOSOFT_STILL_STARTING_HEADER)));
progressBar = new JFXProgressBar();
progressBar = new ProgressBar();
progressBar.setPrefHeight(50);
progressLabel = new Label();
@ -79,11 +79,7 @@ public class StartProgressWindow extends Application {
stage.toFront();
});
while (progress.getCount() > 0) {
try {
progress.waitForChange();
} catch (InterruptedException e) {
e.printStackTrace();
}
progress.waitForChange();
Platform.runLater(() -> {
progressBar.setProgress(1.0F - ((float) progress.getCount() / progress.getTotal()));
progressLabel.setText(String.format("%d / %d", (progress.getTotal() - progress.getCount()), progress.getTotal()));

View File

@ -93,6 +93,8 @@ class RenderWindow(
var tickCount = 0L
var lastTickTimer = System.currentTimeMillis()
private var initalPositionReceived = false
init {
connection.registerEvent(CallbackEventInvoker.of<ConnectionStateChangeEvent> {
if (it.connection.isDisconnected) {
@ -106,8 +108,9 @@ class RenderWindow(
if (packet !is PositionAndRotationS2CP) {
return@of
}
if (latch.count > 0) {
latch.countDown()
if (!initalPositionReceived) {
latch.dec()
initalPositionReceived = true
}
queue += {
inputHandler.camera.setPosition(packet.position)
@ -270,11 +273,11 @@ class RenderWindow(
connection.fireEvent(ScreenResizeEvent(previousScreenDimensions = Vec2i(0, 0), screenDimensions = screenDimensions))
Log.log(LogMessageType.RENDERING_LOADING) { "Rendering is fully prepared in ${stopwatch.totalTime()}" }
Log.log(LogMessageType.RENDERING_LOADING) { "Rendering is fully prepared in ${stopwatch.totalTime()}" }
initialized = true
latch.countDown()
latch.waitUntilZero()
this.latch.waitUntilZero()
latch.dec()
latch.await()
this.latch.await()
glfwShowWindow(windowId)
Log.log(LogMessageType.RENDERING_GENERAL) { "Showing window after ${stopwatch.totalTime()}" }
}

View File

@ -13,6 +13,7 @@
package de.bixilon.minosoft.gui.rendering
import de.bixilon.minosoft.Minosoft
import de.bixilon.minosoft.gui.rendering.sound.AudioPlayer
import de.bixilon.minosoft.protocol.network.connection.PlayConnection
import de.bixilon.minosoft.protocol.protocol.ConnectionStates
@ -28,16 +29,19 @@ class Rendering(private val connection: PlayConnection) {
fun init(latch: CountUpAndDownLatch) {
Log.log(LogMessageType.RENDERING_GENERAL, LogLevels.INFO) { "Hello LWJGL ${Version.getVersion()}!" }
latch.countUp()
latch.inc()
startRenderThread(latch)
latch.countUp()
startAudioPlayerThread(latch)
}
private fun startAudioPlayerThread(latch: CountUpAndDownLatch) {
if (!Minosoft.config.config.game.sound.enabled) {
return
}
val audioLatch = CountUpAndDownLatch(1, latch)
Thread({
try {
audioPlayer.init(latch)
audioPlayer.init(audioLatch)
audioPlayer.startLoop()
audioPlayer.exit()
} catch (exception: Throwable) {
@ -46,6 +50,7 @@ class Rendering(private val connection: PlayConnection) {
audioPlayer.exit()
} catch (ignored: Throwable) {
}
latch.minus(audioLatch.count)
}
}, "Audio#${connection.connectionId}").start()
}

View File

@ -16,6 +16,7 @@ package de.bixilon.minosoft.gui.rendering.sound
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import de.bixilon.minosoft.Minosoft
import de.bixilon.minosoft.data.mappings.ResourceLocation
import de.bixilon.minosoft.data.mappings.sounds.SoundEvent
import de.bixilon.minosoft.gui.rendering.Rendering
@ -23,6 +24,7 @@ import de.bixilon.minosoft.gui.rendering.input.camera.Camera
import de.bixilon.minosoft.gui.rendering.modding.events.CameraPositionChangeEvent
import de.bixilon.minosoft.gui.rendering.sound.sounds.Sound
import de.bixilon.minosoft.gui.rendering.sound.sounds.SoundList
import de.bixilon.minosoft.gui.rendering.util.VecUtil.EMPTY
import de.bixilon.minosoft.gui.rendering.util.VecUtil.toVec3
import de.bixilon.minosoft.modding.event.CallbackEventInvoker
import de.bixilon.minosoft.modding.event.events.PlaySoundEvent
@ -45,7 +47,6 @@ import org.lwjgl.openal.EXTThreadLocalContext.alcSetThreadContext
import org.lwjgl.system.MemoryUtil
import java.nio.ByteBuffer
import java.nio.IntBuffer
import java.nio.ShortBuffer
class AudioPlayer(
@ -64,8 +65,6 @@ class AudioPlayer(
private lateinit var listener: SoundListener
private val sources: MutableList<SoundSource> = synchronizedListOf()
private var pcm: ShortBuffer? = null
private fun preloadSounds() {
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Preloading sounds..." }
@ -120,10 +119,13 @@ class AudioPlayer(
initialized = true
latch.countDown()
latch.dec()
}
fun playSoundEvent(soundEvent: SoundEvent, position: Vec3? = null, volume: Float = 1.0f, pitch: Float = 1.0f) {
if (!initialized) {
return
}
playSound(sounds[soundEvent]!!.getRandom(), position, volume, pitch)
}
@ -144,7 +146,7 @@ class AudioPlayer(
}
fun playSound(sound: Sound, position: Vec3? = null, volume: Float = 1.0f, pitch: Float = 1.0f) {
private fun playSound(sound: Sound, position: Vec3? = null, volume: Float = 1.0f, pitch: Float = 1.0f) {
queue += add@{
sound.load(connection.assetsManager)
if (sound.loadFailed) {
@ -159,12 +161,12 @@ class AudioPlayer(
source.relative = false
source.position = it
} ?: let {
source.position = Vec3(0, 0, 0)
source.position = Vec3.EMPTY
source.relative = true
}
source.sound = sound
source.pitch = pitch * sound.pitch
source.gain = volume * sound.volume
source.gain = volume * sound.volume * Minosoft.config.config.game.sound.masterVolume
source.play()
}
}
@ -191,7 +193,6 @@ class AudioPlayer(
}
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Destroying OpenAL context..." }
MemoryUtil.memFree(pcm)
alcSetThreadContext(MemoryUtil.NULL)
alcDestroyContext(context)

View File

@ -69,7 +69,7 @@ public class ModLoader {
return;
}
progress.addCount(MOD_MAP.size() * ModPhases.values().length); // count * mod phases
progress.setCount(progress.getCount() + MOD_MAP.size() * ModPhases.values().length); // count * mod phases
// check if all dependencies are available
modLoop:
@ -128,7 +128,7 @@ public class ModLoader {
Minosoft.THREAD_POOL.execute(() -> {
if (!entry.getValue().isEnabled()) {
modLatch.countDown();
progress.countDown();
progress.dec();
return;
}
Log.log(LogMessageType.MOD_LOADING, LogLevels.VERBOSE, () -> "Loading mod " + entry.getValue().getInfo() + "in " + phase);
@ -142,7 +142,7 @@ public class ModLoader {
entry.getValue().setEnabled(false);
}
modLatch.countDown();
progress.countDown();
progress.dec();
});
}
modLatch.await();
@ -162,7 +162,7 @@ public class ModLoader {
MinosoftMod instance;
try {
Log.log(LogMessageType.MOD_LOADING, LogLevels.VERBOSE, () -> "Trying to load " + file.getAbsolutePath());
progress.countUp();
progress.inc();
ZipFile zipFile = new ZipFile(file);
ModInfo modInfo = new ModInfo(Util.readJsonFromZip("mod.json", zipFile));
if (isModLoaded(modInfo)) {
@ -182,7 +182,7 @@ public class ModLoader {
e.printStackTrace();
Log.log(LogMessageType.MOD_LOADING, LogLevels.WARN, () -> "Could not load " + file.getAbsolutePath());
}
progress.countDown(); // failed
progress.dec(); // failed
return instance;
}

View File

@ -158,10 +158,9 @@ class PlayConnection(
if (!RenderConstants.DISABLE_RENDERING && !StaticConfiguration.HEADLESS_MODE) {
val renderer = Rendering(this)
this.renderer = renderer
renderer.init(latch)
while (!renderer.renderWindow.initialized || !renderer.audioPlayer.initialized) {
latch.waitForChange()
}
val renderLatch = CountUpAndDownLatch(0, latch)
renderer.init(renderLatch)
renderLatch.awaitWithChange()
}
Log.log(LogMessageType.NETWORK_STATUS, level = LogLevels.INFO) { "Connecting to server: $address" }
network.connect(address)
@ -172,7 +171,7 @@ class PlayConnection(
lastException = MappingsLoadingException("Mappings could not be loaded", exception)
connectionState = ConnectionStates.FAILED_NO_RETRY
}
latch.countDown()
latch.dec()
}

View File

@ -12,6 +12,7 @@
*/
package de.bixilon.minosoft.protocol.packets.s2c.play
import de.bixilon.minosoft.Minosoft
import de.bixilon.minosoft.data.SoundCategories
import de.bixilon.minosoft.data.mappings.sounds.SoundEvent
import de.bixilon.minosoft.modding.event.events.PlaySoundEvent
@ -55,6 +56,9 @@ class SoundEventS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
}
override fun handle(connection: PlayConnection) {
if (!Minosoft.config.config.game.sound.enablePacketSounds) {
return
}
connection.fireEvent(PlaySoundEvent(connection, this))
}

View File

@ -85,7 +85,7 @@ public class CLI {
.build();
latch.countDown();
latch.dec();
while (true) {
try {
@ -118,6 +118,6 @@ public class CLI {
e.printStackTrace();
}
}, "CLI").start();
latch.waitUntilZero();
latch.await();
}
}

View File

@ -1,101 +0,0 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.util;
// Thanks https://stackoverflow.com/questions/14255019/latch-that-can-be-incremented
public class CountUpAndDownLatch {
private final Object lock = new Object();
private long count;
private long total;
public CountUpAndDownLatch(int count) {
this.total = count;
this.count = count;
}
public void waitUntilZero() throws InterruptedException {
synchronized (this.lock) {
while (this.count > 0) {
this.lock.wait();
}
}
}
public void waitUntilZero(long timeout) throws InterruptedException {
synchronized (this.lock) {
while (this.count > 0) {
this.lock.wait(timeout);
}
}
}
public void countUp() {
synchronized (this.lock) {
this.total++;
this.count++;
this.lock.notifyAll();
}
}
public void countDown() {
if (this.count == 0) {
throw new IllegalStateException("Can not count down, counter is already 0");
}
synchronized (this.lock) {
this.count--;
this.lock.notifyAll();
}
}
public long getCount() {
synchronized (this.lock) {
return this.count;
}
}
public void setCount(int value) {
synchronized (this.lock) {
this.total += value;
this.count = value;
this.lock.notifyAll();
}
}
public void addCount(int count) {
synchronized (this.lock) {
this.total += count;
this.count += count;
this.lock.notifyAll();
}
}
public long getTotal() {
return this.total;
}
public void waitForChange() throws InterruptedException {
long latestCount = this.count;
long latestTotal = this.total;
synchronized (this.lock) {
while (latestCount == this.count && latestTotal == this.total) {
this.lock.wait();
}
}
}
@Override
public String toString() {
return String.format("%d / %d", this.count, this.total);
}
}

View File

@ -0,0 +1,135 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.util
// Thanks https://stackoverflow.com/questions/14255019/latch-that-can-be-incremented
class CountUpAndDownLatch @JvmOverloads constructor(count: Int, var parent: CountUpAndDownLatch? = null) {
private val lock = Object()
private val children: MutableSet<CountUpAndDownLatch> = mutableSetOf()
var count: Int = 0
get() {
synchronized(lock) {
return field
}
}
set(value) {
synchronized(lock) {
val diff = value - field
check(value >= 0) { "Can not set count (previous=$field, value=$value)" }
if (diff > 0) {
total += diff
}
field = value
parent?.plus(diff) ?: notify()
}
}
var total: Int = count
get() {
synchronized(lock) {
return field
}
}
private set(value) {
check(value >= 0) { "Total can not be < 0: $value" }
synchronized(lock) {
check(value >= field) { "Total can not decrement! (current=$field, wanted=$value)" }
field = value
}
}
init {
check(parent !== this)
parent?.addChild(this)
this.count += count
}
fun addChild(latch: CountUpAndDownLatch) {
synchronized(lock) {
latch.parent = this
children += latch
}
}
@JvmOverloads
fun await(timeout: Long = 0L) {
synchronized(lock) {
while (count > 0) {
lock.wait(timeout)
}
}
}
@JvmName(name = "customNotify")
private fun notify(`this`: CountUpAndDownLatch = this) {
synchronized(lock) {
lock.notifyAll()
for (child in children) {
if (child === `this`) {
continue
}
child.notify(this)
}
}
if (`this` === parent) {
return
}
parent?.notify(this)
}
operator fun inc(): CountUpAndDownLatch {
plus(1)
return this
}
operator fun dec(): CountUpAndDownLatch {
minus(1)
return this
}
fun plus(value: Int): CountUpAndDownLatch {
synchronized(lock) {
count += value
}
return this
}
fun minus(value: Int): CountUpAndDownLatch {
return plus(-value)
}
fun waitForChange() {
val lastCount = count
val lastTotal = total
synchronized(lock) {
while (lastCount == count && lastTotal == total) {
lock.wait()
}
}
}
fun awaitWithChange() {
synchronized(lock) {
if (total == 0) {
waitForChange()
}
await()
}
}
override fun toString(): String {
return String.format("%d / %d", count, total)
}
}

View File

@ -90,18 +90,14 @@ public class AsyncTaskWorker {
}
}
this.jobsDone.add(task.getTaskName());
latch.countDown();
latch.dec();
});
doing.remove(task);
}
}));
try {
latch.waitForChange();
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.waitForChange();
}
progress.countDown(); // remove initial value of 1
progress.dec(); // remove initial value of 1
}
public boolean isJobDone(String name) {