Skip to content

Commit

Permalink
refactor: control the parallelism of ChunkScanner (#4600)
Browse files Browse the repository at this point in the history
  • Loading branch information
MukjepScarlet authored Nov 21, 2024
1 parent 53b7d22 commit 5e4fed8
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ import net.ccbluex.liquidbounce.event.events.KeyboardKeyEvent
import net.ccbluex.liquidbounce.event.events.MouseButtonEvent
import net.ccbluex.liquidbounce.event.events.WorldChangeEvent
import net.ccbluex.liquidbounce.event.handler
import net.ccbluex.liquidbounce.features.module.modules.client.ModuleAutoConfig
import net.ccbluex.liquidbounce.features.module.modules.client.ModuleLiquidChat
import net.ccbluex.liquidbounce.features.module.modules.client.ModuleRichPresence
import net.ccbluex.liquidbounce.features.module.modules.client.ModuleTargets
import net.ccbluex.liquidbounce.features.module.modules.client.*
import net.ccbluex.liquidbounce.features.module.modules.combat.*
import net.ccbluex.liquidbounce.features.module.modules.combat.autoarmor.ModuleAutoArmor
import net.ccbluex.liquidbounce.features.module.modules.combat.crystalaura.ModuleCrystalAura
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@
*/
package net.ccbluex.liquidbounce.integration.theme.component.types.minimap

import kotlinx.atomicfu.locks.ReentrantLock
import kotlinx.atomicfu.locks.withLock
import net.ccbluex.liquidbounce.render.engine.Color4b
import net.ccbluex.liquidbounce.render.engine.font.BoundingBox2f
import net.ccbluex.liquidbounce.utils.math.Vec2i
import net.minecraft.client.texture.NativeImage
import net.minecraft.client.texture.NativeImageBackedTexture
import net.minecraft.util.math.ChunkPos
import java.util.concurrent.ConcurrentSkipListSet

/**
* Size of the texture atlas in chunks (size x size)
Expand All @@ -43,9 +44,11 @@ private val NOT_LOADED_ATLAS_POSITION = MinimapTextureAtlasManager.AtlasPosition
class MinimapTextureAtlasManager {
private val texture = NativeImageBackedTexture(ATLAS_SIZE * 16, ATLAS_SIZE * 16, false)
private val availableAtlasPositions = ArrayList<AtlasPosition>(ATLAS_SIZE * ATLAS_SIZE - 1)
private val dirtyAtlasPositions = ConcurrentSkipListSet<AtlasPosition>()
private val dirtyAtlasPositions = hashSetOf<AtlasPosition>()
private val chunkPosAtlasPosMap = hashMapOf<ChunkPos, AtlasPosition>()

private val lock = ReentrantLock()

private var allocated = false

init {
Expand Down Expand Up @@ -79,9 +82,11 @@ class MinimapTextureAtlasManager {
}

fun deallocate(chunkPos: ChunkPos) {
val atlasPosition = chunkPosAtlasPosMap.remove(chunkPos) ?: return
lock.withLock {
val atlasPosition = chunkPosAtlasPosMap.remove(chunkPos) ?: return

availableAtlasPositions.add(atlasPosition)
availableAtlasPositions.add(atlasPosition)
}
}

fun deallocateAll() {
Expand All @@ -106,9 +111,9 @@ class MinimapTextureAtlasManager {
chunkPos: ChunkPos,
editor: (NativeImageBackedTexture, AtlasPosition) -> Unit,
) {
val atlasPosition = getOrAllocate(chunkPos)

dirtyAtlasPositions.add(atlasPosition)
val atlasPosition = lock.withLock {
getOrAllocate(chunkPos).apply(dirtyAtlasPositions::add)
}

editor(texture, atlasPosition)
}
Expand All @@ -119,22 +124,24 @@ class MinimapTextureAtlasManager {
* @return the GLid of the texture
*/
fun prepareRendering(): Int {
if (this.dirtyAtlasPositions.isEmpty()) {
return this.texture.glId
}
lock.withLock {
if (this.dirtyAtlasPositions.isEmpty()) {
return this.texture.glId
}

this.texture.bindTexture()
this.texture.bindTexture()

val dirtyChunks = this.dirtyAtlasPositions.size
val dirtyChunks = this.dirtyAtlasPositions.size

when {
!this.allocated || dirtyChunks >= FULL_UPLOAD_THRESHOLD -> uploadFullTexture()
else -> uploadOnlyDirtyPositions()
}
when {
!this.allocated || dirtyChunks >= FULL_UPLOAD_THRESHOLD -> uploadFullTexture()
else -> uploadOnlyDirtyPositions()
}

this.dirtyAtlasPositions.clear()
this.dirtyAtlasPositions.clear()

return this.texture.glId
return this.texture.glId
}
}

private fun uploadFullTexture() {
Expand Down Expand Up @@ -169,7 +176,7 @@ class MinimapTextureAtlasManager {
}
}

data class AtlasPosition(private val x: Int, private val y: Int) : Comparable<AtlasPosition> {
data class AtlasPosition(private val x: Int, private val y: Int) {
val baseXOnAtlas: Int = x shl 4
val baseYOnAtlas: Int = y shl 4

Expand All @@ -189,14 +196,8 @@ class MinimapTextureAtlasManager {
* @param chunkX x coordinate in the chunk (0-15)
* @param chunkY y coordinate in the chunk (0-15)
*/
fun getPosOnAtlas(
chunkX: Int,
chunkY: Int,
): Vec2i {
return Vec2i(baseXOnAtlas + chunkX, baseYOnAtlas + chunkY)
fun getPosOnAtlas(chunkX: Int, chunkY: Int): Vec2i {
return Vec2i(baseXOnAtlas or chunkX, baseYOnAtlas or chunkY)
}

override fun compareTo(other: AtlasPosition): Int =
compareValuesBy(this, other, AtlasPosition::x, AtlasPosition::y)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ import net.ccbluex.liquidbounce.utils.client.logger
import net.ccbluex.liquidbounce.utils.client.mc
import net.ccbluex.liquidbounce.utils.kotlin.getValue
import net.minecraft.block.BlockState
import net.minecraft.util.Util
import net.minecraft.util.math.BlockPos
import net.minecraft.world.chunk.WorldChunk
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.coroutines.cancellation.CancellationException

object ChunkScanner : Listenable {

private val subscribers = CopyOnWriteArrayList<BlockChangeSubscriber>()

private val loadedChunks = hashSetOf<ChunkLocation>()
Expand Down Expand Up @@ -117,7 +119,16 @@ object ChunkScanner : Listenable {
}

object ChunkScannerThread {
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

/**
* When the first request comes in, the dispatcher and the scope will be initialized,
* and its parallelism cannot be modified
*/
@OptIn(ExperimentalCoroutinesApi::class)
private val dispatcher = Util.getMainWorkerExecutor().asCoroutineDispatcher()
.limitedParallelism((Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(2))

private val scope = CoroutineScope(dispatcher + SupervisorJob())

/**
* Shared cache for CoroutineScope
Expand Down Expand Up @@ -153,16 +164,17 @@ object ChunkScanner : Listenable {
retrying = 0

// process the update request
// TODO: may need to start a new job
when (chunkUpdate) {
is UpdateRequest.ChunkUpdateRequest -> scanChunk(chunkUpdate)
launch {
when (chunkUpdate) {
is UpdateRequest.ChunkUpdateRequest -> scanChunk(chunkUpdate)

is UpdateRequest.ChunkUnloadRequest -> subscribers.forEach {
it.clearChunk(chunkUpdate.x, chunkUpdate.z)
}
is UpdateRequest.ChunkUnloadRequest -> subscribers.forEach {
it.clearChunk(chunkUpdate.x, chunkUpdate.z)
}

is UpdateRequest.BlockUpdateEvent -> subscribers.forEach {
it.recordBlock(chunkUpdate.blockPos, chunkUpdate.newState, cleared = false)
is UpdateRequest.BlockUpdateEvent -> subscribers.forEach {
it.recordBlock(chunkUpdate.blockPos, chunkUpdate.newState, cleared = false)
}
}
}
} catch (e: CancellationException) {
Expand Down

0 comments on commit 5e4fed8

Please sign in to comment.