Difference between revisions of "User:Bnorm/CoroutineRobot"

From Robowiki
Jump to navigation Jump to search
(Base robot which uses Kotlin coroutines)
 
 
Line 32: Line 32:
  
 
     private class QueueCoroutineDispatcher(threadCount: Int) : CoroutineDispatcher() {
 
     private class QueueCoroutineDispatcher(threadCount: Int) : CoroutineDispatcher() {
         private val queue = LinkedBlockingQueue<Runnable?>()
+
        companion object {
 +
            private val POISON = Runnable {}
 +
        }
 +
 
 +
         private val queue = LinkedBlockingQueue<Runnable>()
 
         private val threads = List(threadCount) {
 
         private val threads = List(threadCount) {
 
             thread(name = "Computation $it") {
 
             thread(name = "Computation $it") {
 
                 try {
 
                 try {
 
                     while (true) {
 
                     while (true) {
                         val task = queue.take() ?: break
+
                         val task = queue.take()
 +
                        if (task === POISON) break
 
                         task.run()
 
                         task.run()
 
                     }
 
                     }
Line 53: Line 58:
 
             repeat(threads.size) {
 
             repeat(threads.size) {
 
                 // poison the queue once for each thread
 
                 // poison the queue once for each thread
                 queue.put(null)
+
                 queue.put(POISON)
 
             }
 
             }
 
         }
 
         }

Latest revision as of 05:45, 30 July 2021

CoroutineRobot

Using Kotlin and coroutines, safe and fair multithreading for a Robot can be implemented in Robocode. This can be achieved with structured concurrency and a custom thread dispatcher.

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.runBlocking
import robocode.AdvancedRobot
import java.util.concurrent.LinkedBlockingQueue
import kotlin.concurrent.thread
import kotlin.coroutines.CoroutineContext

abstract class CoroutineRobot : AdvancedRobot() {
    private val _computation = QueueCoroutineDispatcher(4)
    val Computation: CoroutineDispatcher get() = _computation

    private lateinit var _main: CoroutineDispatcher
    val Main: CoroutineDispatcher get() = _main

    final override fun run() {
        try {
            runBlocking {
                _main = coroutineContext[CoroutineDispatcher]!!
                coroutineRun()
            }
        } finally {
            _computation.shutdown()
        }
    }

    abstract suspend fun coroutineRun()

    private class QueueCoroutineDispatcher(threadCount: Int) : CoroutineDispatcher() {
        companion object {
            private val POISON = Runnable {}
        }

        private val queue = LinkedBlockingQueue<Runnable>()
        private val threads = List(threadCount) {
            thread(name = "Computation $it") {
                try {
                    while (true) {
                        val task = queue.take()
                        if (task === POISON) break
                        task.run()
                    }
                } catch (ignore: InterruptedException) {
                    // ignore
                }
            }
        }

        override fun dispatch(context: CoroutineContext, block: Runnable) {
            queue.put(block)
        }

        fun shutdown() {
            repeat(threads.size) {
                // poison the queue once for each thread
                queue.put(POISON)
            }
        }
    }
}

Multithreading

With the above base class multithreading can be achieved using coroutineScope, launch, and withContext builder functions.

import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class SampleRobot : CoroutineRobot() {
    override suspend fun coroutineRun() {
        while (true) {
            coroutineScope { // Starts a logic container for asynchronous work
                launch(Computation) { // Launch asynchronous work on 1 of the 4 computation threads

                    // Do radar logic

                    withContext(Main) { // Switch back to run thread
                        setTurnRadarRightRadians(radarTurn)
                    }
                }
                launch(Computation) { // Launch asynchronous work on 1 of the 4 computation threads

                    // Do firing logic

                    withContext(Main) { // Switch back to run thread
                        setTurnGunRightRadians(gunTurn)
                        setFire(bulletPower)
                    }
                }
                launch(Computation) { // Launch asynchronous work on 1 of the 4 computation threads

                    // Do movement logic

                    withContext(Main) { // Switch back to run thread
                        setTurnRightRadians(tankTurn)
                        setAhead(tankMove)
                    }
                }
            }

            execute()
        }
    }
}