diff --git a/.gitignore b/.gitignore index 4276bdcf..6090b450 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,4 @@ docs/ .idea/artifacts !**/build/generated/ +core/gradle.properties diff --git a/core/src/commonMain/kotlin/dev/schlaubi/lavakord/audio/internal/AbstractLavakord.kt b/core/src/commonMain/kotlin/dev/schlaubi/lavakord/audio/internal/AbstractLavakord.kt index 0f645cbb..06fbcd47 100644 --- a/core/src/commonMain/kotlin/dev/schlaubi/lavakord/audio/internal/AbstractLavakord.kt +++ b/core/src/commonMain/kotlin/dev/schlaubi/lavakord/audio/internal/AbstractLavakord.kt @@ -132,8 +132,11 @@ public abstract class AbstractLavakord internal constructor( } - @Suppress("LeakingThis") - private val loadBalancer: LoadBalancer = LoadBalancer(options.loadBalancer.penaltyProviders, this) + /** + * The load balancer used to select nodes + */ + @Suppress("LeakingThis", "MemberVisibilityCanBePrivate") + public val loadBalancer: LoadBalancer = LoadBalancer(options.loadBalancer.penaltyProviders, this) override val nodes: List get() = nodesMap.values.toList() diff --git a/core/src/commonMain/kotlin/dev/schlaubi/lavakord/audio/internal/LoadBalancer.kt b/core/src/commonMain/kotlin/dev/schlaubi/lavakord/audio/internal/LoadBalancer.kt index 2bf25f76..f112f13a 100644 --- a/core/src/commonMain/kotlin/dev/schlaubi/lavakord/audio/internal/LoadBalancer.kt +++ b/core/src/commonMain/kotlin/dev/schlaubi/lavakord/audio/internal/LoadBalancer.kt @@ -4,22 +4,24 @@ import dev.schlaubi.lavakord.LavaKord import dev.schlaubi.lavakord.audio.Node import kotlin.math.pow -internal class LoadBalancer( +public class LoadBalancer( private val penaltyProviders: List, private val lavakord: LavaKord ) { - fun determineBestNode(guildId: ULong): Node? = lavakord.nodes + internal fun determineBestNode(guildId: ULong): Node? = lavakord.nodes .asSequence() .filter(Node::available) - .minByOrNull { calculatePenalties(it, penaltyProviders, guildId) } + .minByOrNull { calculatePenalties(it, guildId).sum } - // Inspired by: https://github.com/freyacodes/Lavalink-Client/blob/master/src/main/java/lavalink/client/io/LavalinkLoadBalancer.java#L111 - private fun calculatePenalties( + /** + * Calculate the penalties for a given guild + * Adapted from https://github.com/freyacodes/Lavalink-Client/blob/master/src/main/java/lavalink/client/io/LavalinkLoadBalancer.java#L111 + */ + public fun calculatePenalties( node: Node, - penaltyProviders: List, guildId: ULong - ): Int { + ): Penalties { val playerPenalty: Int val cpuPenalty: Int val deficitFramePenalty: Int @@ -36,20 +38,45 @@ internal class LoadBalancer( playerPenalty = stats.playingPlayers cpuPenalty = 1.05.pow(100 * stats.cpu.systemLoad).toInt() * 10 - 10 - if ((stats.frameStats != null) && stats.frameStats?.deficit != 1) { + val frameStats = stats.frameStats + if (frameStats != null) { + val frameDeficit = frameStats.deficit.coerceAtLeast(0).toFloat() + val frameNulls = frameStats.nulled.coerceAtLeast(0).toFloat() + deficitFramePenalty = - (1.03.pow(((500f * (stats.frameStats?.deficit?.toFloat() ?: (0 / 3000f)))).toDouble()) * 600 - 600).toInt() + (1.03.pow( + ((500f * frameDeficit / 3000f)).toDouble() + ) * 600 - 600).toInt() nullFramePenalty = - (1.03.pow(((500f * (stats.frameStats?.nulled?.toFloat() ?: (0 / 3000f)))).toDouble()) * 300 - 300).toInt() * 2 + (1.03.pow( + ((500f * frameNulls / 3000f)).toDouble() + ) * 300 - 300).toInt() * 2 } else { deficitFramePenalty = 0 nullFramePenalty = 0 } } - return playerPenalty + cpuPenalty + deficitFramePenalty + nullFramePenalty + customPenalties + return Penalties(playerPenalty, cpuPenalty, deficitFramePenalty, nullFramePenalty, customPenalties) } } +/** Result of penalties used for load balancing */ +public data class Penalties( + /** Penalty due to number of players */ + val playerPenalty: Int, + /** Penalty due to high CPU */ + val cpuPenalty: Int, + /** Penalty due to Lavalink struggling to send frames */ + val deficitFramePenalty: Int, + /** Penalty due to Lavaplayer struggling to provide frames */ + val nullFramePenalty: Int, + /** Penalties from [PenaltyProvider]*/ + val customPenalties: Int +) { + /** The sum of all penalties */ + val sum: Int = playerPenalty + cpuPenalty + deficitFramePenalty + nullFramePenalty + customPenalties +} + /** * Interface to accept custom penalties for [Node]s. */