All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.robocode.tankroyale.gui.booter.BootProcess.kt Maven / Gradle / Ivy

package dev.robocode.tankroyale.gui.booter

import dev.robocode.tankroyale.gui.model.MessageConstants
import dev.robocode.tankroyale.gui.settings.ConfigSettings
import dev.robocode.tankroyale.gui.settings.ServerSettings
import dev.robocode.tankroyale.gui.util.Event
import dev.robocode.tankroyale.gui.util.ResourceUtil
import java.io.BufferedReader
import java.io.FileNotFoundException
import java.io.InputStreamReader
import java.io.PrintStream
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Paths
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean

object BootProcess {

    val onBootBot = Event()
    val onUnbootBot = Event()

    private const val JAR_FILE_NAME = "robocode-tankroyale-booter"

    private val isBooted = AtomicBoolean(false)
    private var booterProcess: Process? = null
    private var thread: Thread? = null

    private val json = MessageConstants.json

    private val pidAndDirs = ConcurrentHashMap() // pid, dir

    private val bootedBotsList = mutableListOf()

    fun info(botsOnly: Boolean? = false, teamsOnly: Boolean? = false): List {
        val args = mutableListOf(
            "java",
            "-jar",
            getBooterJar(),
            "info",
            "--game-types=${ConfigSettings.gameType.displayName}"
        )
        if (botsOnly == true) {
            args += "--bots-only"
        }
        if (teamsOnly == true) {
            args += "--teams-only"
        }
        botDirs.forEach { args += it }

        val process = ProcessBuilder(args).start()
        startThread(process, false)
        try {
            val jsonStr = String(process.inputStream.readAllBytes(), StandardCharsets.UTF_8)
            return json.decodeFromString(jsonStr)
        } finally {
            stopThread()
        }
    }

    fun boot(botDirNames: List) {
        if (isBooted.get()) {
            bootBotsWithAlreadyBootedProcess(botDirNames)
        } else {
            bootBotProcess(botDirNames)
        }
    }

    fun stop(pids: List) {
        stopBotsWithBootedProcess(pids)
    }

    val bootedBots: List
        get() {
            return bootedBotsList
        }

    val botDirs: List
        get() {
            return ConfigSettings.botDirectories.filter { it.enabled }.map { it.path }
        }

    private fun bootBotProcess(botDirNames: List) {
        val args = mutableListOf(
            "java",
            "-Dserver.url=${ServerSettings.currentServerUrl}",
            "-Dserver.secret=${ServerSettings.botSecrets.first()}",
            "-jar",
            getBooterJar(),
            "boot"
        )
        botDirNames.forEach { args += it }

        booterProcess = ProcessBuilder(args).start()?.also {
            startThread(it, true)
            isBooted.set(true)
        }
    }

    private fun bootBotsWithAlreadyBootedProcess(botDirNames: List) {
        PrintStream(booterProcess?.outputStream!!).also { printStream ->
            botDirNames.forEach { printStream.println("boot $it") }
            printStream.flush()
        }
    }

    private fun stopBotsWithBootedProcess(pids: List) {
        PrintStream(booterProcess?.outputStream!!).also { printStream ->
            pids.forEach { printStream.println("stop $it") }
            printStream.flush()
        }
    }

    fun stop() {
        if (!isBooted.get())
            return

        stopThread()
        isBooted.set(false)

        stopProcess()

        notifyUnbootBotProcesses()

        bootedBotsList.clear()
    }

    private fun stopProcess() {
        booterProcess?.apply {
            if (isAlive) {
                PrintStream(outputStream).apply {
                    println("quit")
                    flush()
                }
            }
        }
        booterProcess = null
    }

    private fun notifyUnbootBotProcesses() {
        pidAndDirs.forEach { onUnbootBot.fire(DirAndPid(it.value, it.key)) }
    }

    private fun getBooterJar(): String {
        System.getProperty("booterJar")?.let {
            Paths.get(it).apply {
                if (Files.exists(this)) {
                    throw FileNotFoundException(toString())
                }
                return toString()
            }
        }
        Paths.get("").apply {
            Files.list(this).filter { it.startsWith(JAR_FILE_NAME) && it.endsWith(".jar") }.findFirst().apply {
                if (isPresent) {
                    return get().toString()
                }
            }
        }
        return try {
            ResourceUtil.getResourceFile("${JAR_FILE_NAME}.jar")?.absolutePath ?: ""
        } catch (ex: Exception) {
            System.err.println(ex.message)
            ""
        }
    }

    private fun readInputToPids(process: Process) {
        process.inputStream?.let {
            val reader = BufferedReader(InputStreamReader(process.inputStream))
            while (thread?.isInterrupted == false) {
                val line = reader.readLine()
                if (line != null && line.isNotBlank()) {
                    if (line.startsWith("stopped ")) {
                        removePid(line)
                    } else {
                        addPid(line)
                    }
                }
            }
        }
    }

    private fun readErrorToStdError(process: Process) {
        val reader = BufferedReader(InputStreamReader(process.errorStream!!, StandardCharsets.UTF_8))
        var line: String?
        while (run {
                line = reader.readLine()
                line
            } != null) {
            System.err.println(line)
        }
    }

    private fun startThread(process: Process, doReadInputToProcessIds: Boolean) {
        thread = Thread {
            while (thread?.isInterrupted == false) {
                try {
                    if (doReadInputToProcessIds)
                        readInputToPids(process)
                    readErrorToStdError(process)
                } catch (e: InterruptedException) {
                    break
                }
            }
        }.apply { start() }
    }

    private fun stopThread() {
        thread?.interrupt()
    }

    private fun addPid(line: String) {
        val pidAndDir = line.split(";", limit = 2)
        if (pidAndDir.size == 2) {
            val pid = pidAndDir[0].toLong()
            val dir = pidAndDir[1]

            pidAndDirs[pid] = dir

            val dirAndPid = DirAndPid(dir, pid)
            bootedBotsList.add(dirAndPid)

            onBootBot.fire(dirAndPid)
        }
    }

    private fun removePid(line: String) {
        val actionAndPid = line.split(" ", limit = 2)
        if (actionAndPid.size == 2) {
            val pid = actionAndPid[1].toLong()
            val dir = pidAndDirs[pid]

            pidAndDirs.remove(pid)

            if (dir != null) {
                val dirAndPid = DirAndPid(dir, pid)
                bootedBotsList.remove(dirAndPid)

                onUnbootBot.fire(dirAndPid)
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy