commit de25bed3906543d6ffa77a8f980a455becd6a72c
Author: leszek <leszek@koltunski.pl>
Date:   Mon Dec 1 14:15:49 2025 +0100

    Convert EffectMessageSender to Kotlin Coroutines.

diff --git a/build.gradle b/build.gradle
index 2476786..9c711d1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -8,7 +8,7 @@ android {
             keyAlias = 'distorted'
         }
     }
-    compileSdk 35
+    compileSdk 36
 
     defaultConfig {
         minSdkVersion 21
@@ -33,4 +33,9 @@ android {
     kotlinOptions {
         jvmTarget = '11'
     }
+
+    dependencies {
+        implementation(libs.kotlinx.coroutines.core.v190)
+    }
+
 }
diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueueFragment.kt b/src/main/java/org/distorted/library/effectqueue/EffectQueueFragment.kt
index 44b8f05..0e8f0c5 100644
--- a/src/main/java/org/distorted/library/effectqueue/EffectQueueFragment.kt
+++ b/src/main/java/org/distorted/library/effectqueue/EffectQueueFragment.kt
@@ -23,7 +23,7 @@ package org.distorted.library.effectqueue
 import android.opengl.GLES30
 import org.distorted.library.effect.EffectType
 import org.distorted.library.effect.FragmentEffect
-import org.distorted.library.message.EffectMessageSender.Companion.newMessage
+import org.distorted.library.message.EffectMessageSender
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 internal class EffectQueueFragment : EffectQueue
@@ -61,7 +61,7 @@ internal class EffectQueueFragment : EffectQueue
 
             for (i in 0..<numEffects)
                 if (mEffects[i]!!.compute(array, NUM_FLOAT_UNIFORMS*i, currTime, step))
-                    newMessage(mEffects[i]!!)
+                    EffectMessageSender.newMessage(mEffects[i]!!)
         }
     }
 
diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueueMatrix.kt b/src/main/java/org/distorted/library/effectqueue/EffectQueueMatrix.kt
index 023e861..ac77dd4 100644
--- a/src/main/java/org/distorted/library/effectqueue/EffectQueueMatrix.kt
+++ b/src/main/java/org/distorted/library/effectqueue/EffectQueueMatrix.kt
@@ -24,7 +24,7 @@ import android.opengl.GLES30
 import org.distorted.library.effect.EffectType
 import org.distorted.library.effect.MatrixEffect
 import org.distorted.library.helpers.MatrixHelper.multiply
-import org.distorted.library.message.EffectMessageSender.Companion.newMessage
+import org.distorted.library.message.EffectMessageSender
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -66,7 +66,7 @@ internal class EffectQueueMatrix : EffectQueue
 
             for (i in 0..<numEffects)
                 if (mEffects[i]!!.compute(array, NUM_FLOAT_UNIFORMS*i, currTime, step))
-                    newMessage(mEffects[i]!!)
+                    EffectMessageSender.newMessage(mEffects[i]!!)
         }
     }
 
diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.kt b/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.kt
index 2cba968..fa0a6cb 100644
--- a/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.kt
+++ b/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.kt
@@ -30,7 +30,7 @@ import org.distorted.library.main.DistortedNode
 import org.distorted.library.main.InternalOutputSurface
 import org.distorted.library.main.InternalRenderState
 import org.distorted.library.mesh.MeshBase
-import org.distorted.library.message.EffectMessageSender.Companion.newMessage
+import org.distorted.library.message.EffectMessageSender
 import org.distorted.library.program.DistortedProgram
 import java.io.InputStream
 
@@ -136,7 +136,7 @@ class EffectQueuePostprocess : EffectQueue
             val effect = mEffects[i] as PostprocessEffect
 
             if (effect.compute(array, NUM_FLOAT_UNIFORMS*i, currTime, step))
-                newMessage(mEffects[i]!!)
+                EffectMessageSender.newMessage(mEffects[i]!!)
 
             halo = array[NUM_FLOAT_UNIFORMS*i].toInt()
             if (halo>mHalo) mHalo = halo
diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueueVertex.kt b/src/main/java/org/distorted/library/effectqueue/EffectQueueVertex.kt
index 7062d56..10c9b85 100644
--- a/src/main/java/org/distorted/library/effectqueue/EffectQueueVertex.kt
+++ b/src/main/java/org/distorted/library/effectqueue/EffectQueueVertex.kt
@@ -23,7 +23,7 @@ package org.distorted.library.effectqueue
 import android.opengl.GLES30
 import org.distorted.library.effect.EffectType
 import org.distorted.library.effect.VertexEffect
-import org.distorted.library.message.EffectMessageSender.Companion.newMessage
+import org.distorted.library.message.EffectMessageSender
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /** Not part of public API, do not document (public only because has to be used in Meshes)
@@ -71,7 +71,7 @@ class EffectQueueVertex : EffectQueue
 
             for (i in 0..<numEffects)
                 if (mEffects[i]!!.compute(array, NUM_FLOAT_UNIFORMS*i, currTime, step))
-                    newMessage(mEffects[i]!!)
+                    EffectMessageSender.newMessage(mEffects[i]!!)
 
             mUBF!!.invalidate()
         }
diff --git a/src/main/java/org/distorted/library/main/DistortedLibrary.kt b/src/main/java/org/distorted/library/main/DistortedLibrary.kt
index db2251b..260cc2c 100644
--- a/src/main/java/org/distorted/library/main/DistortedLibrary.kt
+++ b/src/main/java/org/distorted/library/main/DistortedLibrary.kt
@@ -43,7 +43,7 @@ import org.distorted.library.mesh.DeferredJobs
 import org.distorted.library.mesh.MeshBase
 import org.distorted.library.mesh.MeshBase.Companion.maxEffComponents
 import org.distorted.library.mesh.MeshBase.Companion.useCenters
-import org.distorted.library.message.EffectMessageSender.Companion.startSending
+import org.distorted.library.message.EffectMessageSender
 import org.distorted.library.program.DistortedProgram
 import org.distorted.library.program.VertexCompilationException
 import org.distorted.library.type.Dynamic
@@ -1038,7 +1038,7 @@ object DistortedLibrary
         mOITCompilationAttempted = false
 
         detectBuggyDriversAndSetQueueSize(queueSize)
-        startSending()
+        EffectMessageSender.startSending()
 
         try
         {
diff --git a/src/main/java/org/distorted/library/main/InternalStackFrameList.kt b/src/main/java/org/distorted/library/main/InternalStackFrameList.kt
index 6954883..1f3b5ad 100644
--- a/src/main/java/org/distorted/library/main/InternalStackFrameList.kt
+++ b/src/main/java/org/distorted/library/main/InternalStackFrameList.kt
@@ -20,7 +20,7 @@
 package org.distorted.library.main
 
 import org.distorted.library.main.InternalMaster.Slave
-import org.distorted.library.message.EffectMessageSender.Companion.stopSending
+import org.distorted.library.message.EffectMessageSender
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
@@ -132,7 +132,7 @@ object InternalStackFrameList
         }
 
         isInitialized = false
-        if (num<2) stopSending()
+        if (num<2) EffectMessageSender.stopSending()
     }
 
     ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/message/EffectMessageSender.kt b/src/main/java/org/distorted/library/message/EffectMessageSender.kt
index eff4797..51367cd 100644
--- a/src/main/java/org/distorted/library/message/EffectMessageSender.kt
+++ b/src/main/java/org/distorted/library/message/EffectMessageSender.kt
@@ -21,134 +21,45 @@
 package org.distorted.library.message
 
 import org.distorted.library.effect.Effect
-import java.util.Vector
-import kotlin.concurrent.Volatile
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.Channel
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used in Meshes)
- *
- * @y.exclude
- */
-class EffectMessageSender
-private constructor() : Thread()
+
+object EffectMessageSender
 {
-    private class Message (var mListener: EffectListener, var mEffectID: Long)
+    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
+    private val channel = Channel<Message>(Channel.UNLIMITED)
+    private data class Message(val listener: EffectListener, val effectID: Long)
+    private var job: Job? = null
 
-    override fun run()
+    fun startSending()
     {
-        var tmp: Message
-
-        while (mThis!=null)
+        if( job==null )
         {
-            while (!mList!!.isEmpty())
-            {
-                tmp = mList!!.removeAt(0)
-                tmp.mListener.effectFinished(tmp.mEffectID)
-            }
-
-            synchronized(mLock)
-            {
-                if (!mNotify)
-                {
-                    try { mLock.wait() }
-                    catch (_: InterruptedException) { }
-                }
-                mNotify = false
+            job = scope.launch {
+                for (msg in channel) msg.listener.effectFinished(msg.effectID)
             }
         }
-
-        mList!!.clear()
     }
 
-    companion object
+    fun stopSending()
     {
-        private val mLock = Object()
-        private var mList: Vector<Message>? = null
-        private var mThis: EffectMessageSender? = null
-
-        @Volatile
-        private var mNotify = false
-
-        // debug only, to be removed later
-        private var mNumStarts       = 0
-        private var mStartTime: Long = 0
-        private var mStopTime: Long  = 0
-
-        fun startSending()
-        {
-            synchronized(mLock)
-            {
-                if (mThis==null)
-                {
-                    mStartTime = System.currentTimeMillis()
-                    mNumStarts++
-
-                    mList = Vector()
-                    mThis = EffectMessageSender()
-                    mThis!!.start()
-                }
-            }
-        }
-
-        fun stopSending()
-        {
-            synchronized(mLock)
-            {
-                if (mThis!=null)
-                {
-                    mStopTime = System.currentTimeMillis()
-                    mNumStarts--
+        job?.cancel()
+        job = null
 
-                    mThis = null
-                    mLock.notify()
-                }
-            }
-        }
-
-        fun newMessage(effect: Effect)
-        {
-            val numListeners = effect.numListeners
-
-            if (numListeners>0)
-            {
-                val id = effect.iD
-
-                for (i in 0 until numListeners)
-                {
-                    val listener = effect.removeFirstListener()
-                    val msg = Message(listener!!, id)
-                    mList!!.add(msg)
-                }
-
-                synchronized(mLock)
-                {
-                    mNotify = true
-                    mLock.notify()
-                }
-            }
-        }
-
-        val isRunning: Boolean
-            get() = mThis!=null
-
-        fun restartThread()
+        while (true) // drain the channel
         {
-            synchronized(mLock)
-            {
-                if (mThis==null)
-                {
-                    if (mList==null) mList = Vector()
-                    mThis = EffectMessageSender()
-                    mThis!!.start()
-                }
-            }
+            val msg = channel.tryReceive().getOrNull() ?: break
         }
+    }
 
-        fun reportState(): String
+    fun newMessage(effect: Effect)
+    {
+        repeat(effect.numListeners)
         {
-            return "running "+(mThis!=null)+" notify="+mNotify+" elements="+mList!!.size+
-                    " start="+mStartTime+" stop="+mStopTime+" numStarts="+mNumStarts
+            val listener = effect.removeFirstListener() ?: return
+            channel.trySend(Message(listener, effect.iD))
         }
     }
 }
