Skip to content

Commit 5850195

Browse files
committed
fix: Rework transform queue to ensure no duplicate renders occur
Previously a single applyTransforms could cause multiple renderAndOpen calls fairly commonly
1 parent e8697f6 commit 5850195

File tree

1 file changed

+43
-45
lines changed

1 file changed

+43
-45
lines changed

interfaces/src/main/kotlin/com/noxcrew/interfaces/view/AbstractInterfaceView.kt

+43-45
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import com.noxcrew.interfaces.properties.Trigger
1515
import com.noxcrew.interfaces.transform.AppliedTransform
1616
import com.noxcrew.interfaces.utilities.CollapsablePaneMap
1717
import com.noxcrew.interfaces.utilities.forEachInGrid
18+
import kotlinx.coroutines.Job
19+
import kotlinx.coroutines.async
1820
import kotlinx.coroutines.launch
21+
import kotlinx.coroutines.sync.Mutex
1922
import kotlinx.coroutines.sync.Semaphore
2023
import kotlinx.coroutines.withTimeout
2124
import net.kyori.adventure.text.Component
@@ -27,7 +30,7 @@ import org.bukkit.inventory.Inventory
2730
import org.bukkit.inventory.ItemStack
2831
import org.slf4j.LoggerFactory
2932
import java.util.WeakHashMap
30-
import java.util.concurrent.ConcurrentHashMap
33+
import java.util.concurrent.ConcurrentLinkedQueue
3134
import java.util.concurrent.atomic.AtomicBoolean
3235
import java.util.concurrent.atomic.AtomicInteger
3336
import kotlin.time.Duration
@@ -68,8 +71,9 @@ public abstract class AbstractInterfaceView<I : InterfacesInventory, T : Interfa
6871
private val shouldBeOpened = AtomicBoolean(false)
6972
private val openIfClosed = AtomicBoolean(false)
7073

71-
private val pendingTransforms = ConcurrentHashMap.newKeySet<AppliedTransform<P>>()
72-
private val debouncedTransforms = ConcurrentHashMap.newKeySet<AppliedTransform<P>>()
74+
private val pendingTransforms = ConcurrentLinkedQueue<AppliedTransform<P>>()
75+
private var transformingJob: Job? = null
76+
private val transformMutex = Mutex()
7377

7478
private val panes = CollapsablePaneMap.create(backing.createPane())
7579
internal lateinit var pane: CompletedPane
@@ -221,55 +225,49 @@ public abstract class AbstractInterfaceView<I : InterfacesInventory, T : Interfa
221225
}
222226

223227
private fun applyTransforms(transforms: Collection<AppliedTransform<P>>): Boolean {
224-
// Remove all these from the debounced transforms so we can try running
225-
// them again!
226-
debouncedTransforms -= transforms.toSet()
228+
// Ignore if the transforms are empty
229+
if (transforms.isEmpty()) return true
227230

228231
// Check if the player is offline or the server stopping
229232
if (Bukkit.isStopping() || !player.isOnline) return false
230233

231-
transforms.forEach { transform ->
232-
// If the transform is already pending we debounce it
233-
if (transform in pendingTransforms) {
234-
debouncedTransforms += transform
235-
return@forEach
236-
}
237-
238-
// Indicate this transform is running which prevents the menu
239-
// from rendering until all transforms are done!
240-
pendingTransforms += transform
241-
242-
SCOPE.launch {
243-
try {
244-
// Don't run transforms for an offline player!
245-
if (!Bukkit.isStopping() && player.isOnline) {
246-
withTimeout(6.seconds) {
247-
runTransformAndApplyToPanes(transform)
248-
}
249-
}
250-
} catch (exception: Exception) {
251-
logger.error("Failed to run and apply transform: $transform", exception)
252-
} finally {
253-
// Update that this transform has finished and check if
254-
// we are ready to draw the screen finally!
255-
pendingTransforms -= transform
256-
257-
if (transform in debouncedTransforms && applyTransforms(listOf(transform))) {
258-
// Simply run the transform again here and do nothing else
259-
} else {
260-
// If all transforms are done we can finally draw and open the menu
261-
if (pendingTransforms.isEmpty()) {
262-
renderAndOpen()
234+
// Queue up the transforms
235+
pendingTransforms.addAll(transforms)
236+
237+
// Check if the job is already running
238+
SCOPE.launch {
239+
try {
240+
transformMutex.lock()
241+
242+
// Start the job if it's not running currently!
243+
if (transformingJob == null || transformingJob?.isCompleted == true) {
244+
transformingJob = SCOPE.async {
245+
// Go through all pending transforms one at a time until
246+
// we're fully done with all of them. Other threads may
247+
// add additional ones as we go through the queue.
248+
while (pendingTransforms.isNotEmpty()) {
249+
// Removes the first pending transform
250+
val transform = pendingTransforms.remove()
251+
252+
try {
253+
// Don't run transforms for an offline player!
254+
if (!Bukkit.isStopping() && player.isOnline) {
255+
withTimeout(6.seconds) {
256+
runTransformAndApplyToPanes(transform)
257+
}
258+
}
259+
} catch (exception: Exception) {
260+
logger.error("Failed to run and apply transform: $transform", exception)
261+
}
263262
}
263+
264+
// After we have finished running all transforms we render and open
265+
// the menu before ending this job.
266+
renderAndOpen()
264267
}
265268
}
266-
}
267-
}
268-
269-
// In the case that transforms was empty we might be able to open the menu already
270-
if (pendingTransforms.isEmpty()) {
271-
SCOPE.launch {
272-
renderAndOpen()
269+
} finally {
270+
transformMutex.unlock()
273271
}
274272
}
275273
return true

0 commit comments

Comments
 (0)