summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorSoniEx2 <endermoneymod@gmail.com>2025-03-19 15:46:15 -0300
committerSoniEx2 <endermoneymod@gmail.com>2025-03-19 15:46:15 -0300
commitee651c9014db7593fa62e22d0a28bb94bf9f57f5 (patch)
tree542c8901698fb6fe41b408d56d0280f406e64ca6
parent02321a466d5cd069d7f969f51d9485e343d6fae9 (diff)
Don't cross-thread entities, fix flite loading
-rw-r--r--src/client/kotlin/space/autistic/radio/client/flite/FliteFactory.kt3
-rw-r--r--src/client/kotlin/space/autistic/radio/client/fmsim/FmFullThread.kt260
-rw-r--r--src/client/kotlin/space/autistic/radio/client/sound/PirateRadioSoundInstance.kt7
3 files changed, 140 insertions, 130 deletions
diff --git a/src/client/kotlin/space/autistic/radio/client/flite/FliteFactory.kt b/src/client/kotlin/space/autistic/radio/client/flite/FliteFactory.kt
index 993cbe9..89b9656 100644
--- a/src/client/kotlin/space/autistic/radio/client/flite/FliteFactory.kt
+++ b/src/client/kotlin/space/autistic/radio/client/flite/FliteFactory.kt
@@ -9,6 +9,7 @@ import space.autistic.radio.wasm.Bindings
 import space.autistic.radio.wasm.WasmExitException
 import java.io.InputStream
 import java.lang.invoke.MethodHandles
+import kotlin.io.path.inputStream
 import kotlin.reflect.jvm.javaMethod
 
 object FliteFactory : () -> Instance {
@@ -51,7 +52,7 @@ object FliteFactory : () -> Instance {
 
     private fun getModuleInputStream(): InputStream {
         return FabricLoader.getInstance().getModContainer("pirate-radio").flatMap { it.findPath("flite.wasm") }
-            .map<InputStream?> { it.toFile().inputStream() }.orElseGet {
+            .map { it.inputStream() }.orElseGet {
                 this.javaClass.getResourceAsStream("/flite.wasm")
             }
     }
diff --git a/src/client/kotlin/space/autistic/radio/client/fmsim/FmFullThread.kt b/src/client/kotlin/space/autistic/radio/client/fmsim/FmFullThread.kt
index 51b946d..d3b39b8 100644
--- a/src/client/kotlin/space/autistic/radio/client/fmsim/FmFullThread.kt
+++ b/src/client/kotlin/space/autistic/radio/client/fmsim/FmFullThread.kt
@@ -11,12 +11,13 @@ import space.autistic.radio.entity.DisposableTransmitterEntity
 import java.nio.ByteBuffer
 import java.nio.ByteOrder
 import java.nio.FloatBuffer
+import java.util.UUID
 import java.util.concurrent.ArrayBlockingQueue
 import kotlin.math.max
 
 object FmFullThread : Runnable {
     class FmTask(
-        val trackedTransmitters: Map<DisposableTransmitterEntity, PirateRadioSoundInstance.TrackedTransmitter>,
+        val trackedTransmitters: Map<UUID, PirateRadioSoundInstance.TrackedTransmitter>,
         val noiseLevels: FloatArray,
     )
 
@@ -35,12 +36,13 @@ object FmFullThread : Runnable {
 
     // 3 seconds
     private const val REPEAT_TIMEOUT = 8000 * 3
+
     // default to 0.05s (1/20)
     private val bufferSize = System.getProperty("space.autistic.radio.buffer.size", "").toIntOrNull() ?: 2400
 
     override fun run() {
         var currentTask = EMPTY_TASK
-        val modulators = HashMap<DisposableTransmitterEntity, TtsModulator>()
+        val modulators = HashMap<UUID, TtsModulator>()
         val mixingBuffers = Array(3) { FloatBuffer.allocate(FmFullConstants.FFT_DATA_BLOCK_SIZE_48K_300K * 2) }
 
         val inputBuffer = FloatBuffer.allocate(FmFullConstants.FFT_DATA_BLOCK_SIZE_LPF_48K_15K_3K1 * 2)
@@ -64,157 +66,163 @@ object FmFullThread : Runnable {
             null
         }
 
-        while (!Thread.interrupted()) {
-            currentTask = trackedTransmitterQueue.poll() ?: currentTask
-            if (currentTask === EMPTY_TASK) {
-                Thread.onSpinWait()
-                continue
-            }
-            modulators.keys.retainAll(currentTask.trackedTransmitters.keys)
-            currentTask.trackedTransmitters.forEach { (k, v) ->
-                modulators.compute(k) { _, modulator ->
-                    if (modulator != null) {
-                        modulator.power = v.power
-                        modulator.mixingBuffer = mixingBuffers[v.frequencyOffset + 1]
-                        return@compute modulator
-                    }
-                    val audioData = v.audio.getNow(null)
-                    if (audioData != null) {
-                        val buf = FloatBuffer.wrap(audioData)
-                        var actualSampleOffset = Math.floorMod(v.sampleOffset, (buf.capacity() + REPEAT_TIMEOUT))
-                        var repeatTimeout = max(0, actualSampleOffset - buf.capacity())
-                        if (repeatTimeout > 0) {
-                            actualSampleOffset = 0
-                            repeatTimeout = REPEAT_TIMEOUT - repeatTimeout
+        try {
+            while (!Thread.interrupted()) {
+                currentTask = trackedTransmitterQueue.poll() ?: currentTask
+                if (currentTask === EMPTY_TASK) {
+                    Thread.onSpinWait()
+                    continue
+                }
+                modulators.keys.retainAll(currentTask.trackedTransmitters.keys)
+                currentTask.trackedTransmitters.forEach { (k, v) ->
+                    modulators.compute(k) { _, modulator ->
+                        if (modulator != null) {
+                            modulator.power = v.power
+                            modulator.mixingBuffer = mixingBuffers[v.frequencyOffset + 1]
+                            return@compute modulator
                         }
-                        if (actualSampleOffset == buf.capacity()) {
-                            actualSampleOffset = 0
-                            repeatTimeout = REPEAT_TIMEOUT
+                        val audioData = v.audio.getNow(null)
+                        if (audioData != null) {
+                            val buf = FloatBuffer.wrap(audioData)
+                            var actualSampleOffset = Math.floorMod(v.sampleOffset, (buf.capacity() + REPEAT_TIMEOUT))
+                            var repeatTimeout = max(0, actualSampleOffset - buf.capacity())
+                            if (repeatTimeout > 0) {
+                                actualSampleOffset = 0
+                                repeatTimeout = REPEAT_TIMEOUT - repeatTimeout
+                            }
+                            if (actualSampleOffset == buf.capacity()) {
+                                actualSampleOffset = 0
+                                repeatTimeout = REPEAT_TIMEOUT
+                            }
+                            buf.position(actualSampleOffset)
+                            TtsModulator(
+                                buf, FmFullModulator(), v.power, repeatTimeout, mixingBuffers[v.frequencyOffset + 1]
+                            )
+                        } else {
+                            null
                         }
-                        buf.position(actualSampleOffset)
-                        TtsModulator(
-                            buf, FmFullModulator(), v.power, repeatTimeout, mixingBuffers[v.frequencyOffset + 1]
-                        )
-                    } else {
-                        null
                     }
                 }
-            }
 
-            mixingBuffers.forEach {
-                it.clear()
-                while (it.hasRemaining()) it.put(0f)
-                it.clear()
-            }
+                mixingBuffers.forEach {
+                    it.clear()
+                    while (it.hasRemaining()) it.put(0f)
+                    it.clear()
+                }
 
-            modulators.values.forEach {
-                inputBuffer.clear()
-                while (inputBuffer.hasRemaining()) {
-                    val sample = if (it.repeatTimeout > 0) {
-                        it.repeatTimeout--
-                        0f
-                    } else {
-                        it.buffer.get()
-                    }
-                    if (!it.buffer.hasRemaining()) {
-                        it.repeatTimeout = REPEAT_TIMEOUT
-                        it.buffer.clear()
+                modulators.values.forEach {
+                    inputBuffer.clear()
+                    while (inputBuffer.hasRemaining()) {
+                        val sample = if (it.repeatTimeout > 0) {
+                            it.repeatTimeout--
+                            0f
+                        } else {
+                            it.buffer.get()
+                        }
+                        if (!it.buffer.hasRemaining()) {
+                            it.repeatTimeout = REPEAT_TIMEOUT
+                            it.buffer.clear()
+                        }
+                        for (i in 0 until 2 * 6) inputBuffer.put(sample)
                     }
-                    for (i in 0 until 2 * 6) inputBuffer.put(sample)
-                }
-                inputBuffer.clear()
-                val mixingBuffer = it.mixingBuffer
-                it.modulator.process(inputBuffer, it.power, false) { outputBuffer ->
-                    for (i in 0 until mixingBuffer.capacity()) {
-                        mixingBuffer.put(i, mixingBuffer.get(i) + outputBuffer.get())
+                    inputBuffer.clear()
+                    val mixingBuffer = it.mixingBuffer
+                    it.modulator.process(inputBuffer, it.power, false) { outputBuffer ->
+                        for (i in 0 until mixingBuffer.capacity()) {
+                            mixingBuffer.put(i, mixingBuffer.get(i) + outputBuffer.get())
+                        }
                     }
                 }
-            }
 
-            if (modulators.any { it.value.mixingBuffer === mixingBuffers[2] }) {
-                val floatBufferHi = mixingBuffers[2]
-                val floatView = mixingBuffers[1]
-                val z = Vector2f()
-                val w = Vector2f()
-                for (i in 0 until floatBufferHi.capacity() step 2) {
-                    z.x = floatBufferHi.get(i)
-                    z.y = floatBufferHi.get(i + 1)
-                    if (!plus100k.hasRemaining()) {
-                        plus100k.clear()
+                if (modulators.any { it.value.mixingBuffer === mixingBuffers[2] }) {
+                    val floatBufferHi = mixingBuffers[2]
+                    val floatView = mixingBuffers[1]
+                    val z = Vector2f()
+                    val w = Vector2f()
+                    for (i in 0 until floatBufferHi.capacity() step 2) {
+                        z.x = floatBufferHi.get(i)
+                        z.y = floatBufferHi.get(i + 1)
+                        if (!plus100k.hasRemaining()) {
+                            plus100k.clear()
+                        }
+                        w.x = plus100k.get()
+                        w.y = plus100k.get()
+                        z.cmul(w)
+                        floatView.put(i, floatView.get(i) + z.x)
+                        floatView.put(i + 1, floatView.get(i + 1) + z.y)
                     }
-                    w.x = plus100k.get()
-                    w.y = plus100k.get()
-                    z.cmul(w)
-                    floatView.put(i, floatView.get(i) + z.x)
-                    floatView.put(i + 1, floatView.get(i + 1) + z.y)
                 }
-            }
 
-            if (modulators.any { it.value.mixingBuffer === mixingBuffers[0] }) {
-                val floatBufferLo = mixingBuffers[0]
-                val floatView = mixingBuffers[1]
-                val z = Vector2f()
-                val w = Vector2f()
-                for (i in 0 until floatBufferLo.capacity() step 2) {
-                    z.x = floatBufferLo.get(i)
-                    z.y = floatBufferLo.get(i + 1)
-                    if (!minus100k.hasRemaining()) {
-                        minus100k.clear()
+                if (modulators.any { it.value.mixingBuffer === mixingBuffers[0] }) {
+                    val floatBufferLo = mixingBuffers[0]
+                    val floatView = mixingBuffers[1]
+                    val z = Vector2f()
+                    val w = Vector2f()
+                    for (i in 0 until floatBufferLo.capacity() step 2) {
+                        z.x = floatBufferLo.get(i)
+                        z.y = floatBufferLo.get(i + 1)
+                        if (!minus100k.hasRemaining()) {
+                            minus100k.clear()
+                        }
+                        w.x = minus100k.get()
+                        w.y = -minus100k.get()
+                        z.cmul(w)
+                        floatView.put(i, floatView.get(i) + z.x)
+                        floatView.put(i + 1, floatView.get(i + 1) + z.y)
                     }
-                    w.x = minus100k.get()
-                    w.y = -minus100k.get()
-                    z.cmul(w)
-                    floatView.put(i, floatView.get(i) + z.x)
-                    floatView.put(i + 1, floatView.get(i + 1) + z.y)
                 }
-            }
 
-            noiseGens.forEachIndexed { index, v ->
-                if (currentTask.noiseLevels[index] != 0f) {
-                    v.generateNoise(currentTask.noiseLevels[index]) { outputBuffer ->
-                        val mixingBuffer = mixingBuffers[1]
-                        for (i in 0 until mixingBuffer.capacity()) {
-                            mixingBuffer.put(i, mixingBuffer.get(i) + outputBuffer.get())
+                noiseGens.forEachIndexed { index, v ->
+                    if (currentTask.noiseLevels[index] != 0f) {
+                        v.generateNoise(currentTask.noiseLevels[index]) { outputBuffer ->
+                            val mixingBuffer = mixingBuffers[1]
+                            for (i in 0 until mixingBuffer.capacity()) {
+                                mixingBuffer.put(i, mixingBuffer.get(i) + outputBuffer.get())
+                            }
                         }
                     }
                 }
-            }
 
-            noiseFloor.noiseBlock { outputBuffer ->
-                val mixingBuffer = mixingBuffers[1]
-                for (i in 0 until mixingBuffer.capacity()) {
-                    mixingBuffer.put(i, mixingBuffer.get(i) + outputBuffer.get())
+                noiseFloor.noiseBlock { outputBuffer ->
+                    val mixingBuffer = mixingBuffers[1]
+                    for (i in 0 until mixingBuffer.capacity()) {
+                        mixingBuffer.put(i, mixingBuffer.get(i) + outputBuffer.get())
+                    }
                 }
-            }
 
-            demodulator.process(mixingBuffers[1], PirateRadioClient.stereo) { _, audioBuffer ->
-                // TODO stereo pilot
-                // we *want* backpressure
-                // FIXME use bigger buffers?
-                if (ReceiverAudioStream.useNativeAudio) {
-                    while (audioBuffer.hasRemaining()) {
-                        if (!outputBytes.hasRemaining()) {
-                            val written = nativeAudio!!.write(outputBytes.array(), 0, outputBytes.capacity())
-                            outputBytes.position(written).compact()
-                            if (written == 0) {
-                                nativeAudio.start()
-                                continue
+                demodulator.process(mixingBuffers[1], PirateRadioClient.stereo) { _, audioBuffer ->
+                    // TODO stereo pilot
+                    // we *want* backpressure
+                    // FIXME use bigger buffers?
+                    if (ReceiverAudioStream.useNativeAudio) {
+                        while (audioBuffer.hasRemaining()) {
+                            if (!outputBytes.hasRemaining()) {
+                                val written = nativeAudio!!.write(outputBytes.array(), 0, outputBytes.capacity())
+                                outputBytes.position(written).compact()
+                                if (written == 0) {
+                                    nativeAudio.start()
+                                    continue
+                                }
                             }
+                            val volume = PirateRadioClient.volume
+                            outputBytes.putShort(
+                                (MathHelper.clamp(
+                                    (audioBuffer.get() * 32767.5f - 0.5f).toInt(), -32768, 32767
+                                ) * volume * volume / 100).toShort()
+                            )
                         }
-                        val volume = PirateRadioClient.volume
-                        outputBytes.putShort(
-                            (MathHelper.clamp(
-                                (audioBuffer.get() * 32767.5f - 0.5f).toInt(), -32768, 32767
-                            ) * volume * volume / 100).toShort()
+                    } else {
+                        ReceiverAudioStream.bufferQueue.put(
+                            FloatBuffer.allocate(audioBuffer.capacity()).put(audioBuffer).clear()
                         )
                     }
-                } else {
-                    ReceiverAudioStream.bufferQueue.put(
-                        FloatBuffer.allocate(audioBuffer.capacity()).put(audioBuffer).clear()
-                    )
                 }
             }
+        } catch (e: Throwable) {
+            PirateRadio.logger.error("Quitting FM simulation thread, as something went wrong!", e)
+            // for some reason it doesn't print stack trace but we still wanna propagate the exception to the thread
+            // itself
+            throw e
         }
-        PirateRadio.logger.error("Quitting FM simulation thread, as it has been interrupted!")
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/kotlin/space/autistic/radio/client/sound/PirateRadioSoundInstance.kt b/src/client/kotlin/space/autistic/radio/client/sound/PirateRadioSoundInstance.kt
index 4e3fd51..601b458 100644
--- a/src/client/kotlin/space/autistic/radio/client/sound/PirateRadioSoundInstance.kt
+++ b/src/client/kotlin/space/autistic/radio/client/sound/PirateRadioSoundInstance.kt
@@ -15,6 +15,7 @@ import space.autistic.radio.client.fmsim.FmSimulatorMode
 import space.autistic.radio.entity.DisposableTransmitterEntity
 import java.lang.ref.WeakReference
 import java.nio.FloatBuffer
+import java.util.UUID
 import java.util.concurrent.CompletableFuture
 import kotlin.math.PI
 
@@ -68,8 +69,8 @@ class PirateRadioSoundInstance(private val player: ClientPlayerEntity) : MovingS
                 noise + getPowerReceived(entity.pos.squaredDistanceTo(player.pos))
             }
         // updated tracked transmitters
-        val trackedTransmitters: MutableMap<DisposableTransmitterEntity, TrackedTransmitter> = HashMap()
-        listOf(lower, main, upper).flatten().associateWithTo(trackedTransmitters) {
+        val trackedTransmitters: MutableMap<UUID, TrackedTransmitter> = HashMap()
+        listOf(lower, main, upper).flatten().associateTo(trackedTransmitters) {
             val text = it.text
             val audio = futuresCache[text]?.get() ?: CompletableFuture.supplyAsync {
                 lateinit var buffer: FloatBuffer
@@ -83,7 +84,7 @@ class PirateRadioSoundInstance(private val player: ClientPlayerEntity) : MovingS
                 buffer.array()
             }
             futuresCache[text] = WeakReference(audio)
-            TrackedTransmitter(
+            it.uuid to TrackedTransmitter(
                 getPowerReceived(it.pos.squaredDistanceTo(player.pos)),
                 it.age * (8000 / 20),
                 audio,