[terminal] IJ-MR-158015 Refactor: extract FrontendLatencyService

Now most of this complicated logic is in the frontend module.

GitOrigin-RevId: 26456964aa897bfed073addb6064b973e75f07f6
This commit is contained in:
Sergei Tachenov
2025-04-15 07:32:53 +00:00
committed by intellij-monorepo-bot
parent 0fe5626210
commit 9f6e1bd228
8 changed files with 260 additions and 210 deletions
@@ -19,6 +19,10 @@
id="terminalFindPrevious" order="first"/>
<editorActionHandler action="Replace" implementationClass="com.intellij.terminal.frontend.action.TerminalReplaceHandler"
id="terminalReplace" order="first"/>
<applicationService
serviceInterface="org.jetbrains.plugins.terminal.fus.FrontendLatencyService"
serviceImplementation="com.intellij.terminal.frontend.fus.FrontendLatencyServiceImpl"/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.plugins.terminal">
@@ -43,7 +43,7 @@ import org.jetbrains.plugins.terminal.block.reworked.hyperlinks.TerminalHyperlin
import org.jetbrains.plugins.terminal.block.ui.*
import org.jetbrains.plugins.terminal.block.ui.TerminalUi.useTerminalDefaultBackground
import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils
import org.jetbrains.plugins.terminal.fus.ReworkedTerminalUsageCollector
import org.jetbrains.plugins.terminal.fus.FrontendLatencyService
import org.jetbrains.plugins.terminal.util.terminalProjectScope
import java.awt.Component
import java.awt.Dimension
@@ -132,7 +132,7 @@ internal class ReworkedTerminalView(
TerminalBlocksDecorator(outputEditor, blocksModel, scrollingModel, coroutineScope.childScope("TerminalBlocksDecorator"))
outputEditor.putUserData(TerminalBlocksModel.KEY, blocksModel)
val fusActivity = ReworkedTerminalUsageCollector.startFrontendOutputActivity(
val fusActivity = FrontendLatencyService.getInstance().startFrontendOutputActivity(
sessionFuture,
outputEditor = outputEditor as EditorImpl,
alternateBufferEditor = alternateBufferEditor as EditorImpl,
@@ -23,7 +23,7 @@ import org.intellij.lang.annotations.Language
import org.jetbrains.annotations.NonNls
import org.jetbrains.plugins.terminal.block.output.TerminalEventsHandler
import org.jetbrains.plugins.terminal.block.reworked.TerminalSessionModel
import org.jetbrains.plugins.terminal.fus.ReworkedTerminalUsageCollector
import org.jetbrains.plugins.terminal.fus.FrontendLatencyService
import java.awt.AWTEvent
import java.awt.event.InputEvent
import java.awt.event.KeyEvent
@@ -61,7 +61,7 @@ internal abstract class TerminalEventDispatcher(
override fun dispatch(e: AWTEvent): Boolean {
if (e is KeyEvent) {
val fusActivity = ReworkedTerminalUsageCollector.startFrontendTypingActivity(e)
val fusActivity = FrontendLatencyService.getInstance().startFrontendTypingActivity(e)
try {
dispatchKeyEvent(e)
}
@@ -13,7 +13,7 @@ import kotlinx.coroutines.channels.ClosedSendChannelException
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.future.await
import org.jetbrains.plugins.terminal.block.reworked.TerminalSessionModel
import org.jetbrains.plugins.terminal.fus.ReworkedTerminalUsageCollector
import org.jetbrains.plugins.terminal.fus.FrontendLatencyService
import java.nio.charset.StandardCharsets
import java.util.concurrent.CompletableFuture
@@ -37,7 +37,7 @@ class TerminalInput(
capacity = 10000,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
onUndeliveredElement = { event ->
ReworkedTerminalUsageCollector.getFrontendTypingActivityOrNull(event)?.finishTerminalInputEventProcessing()
FrontendLatencyService.getInstance().getFrontendTypingActivityOrNull(event)?.finishTerminalInputEventProcessing()
}
)
@@ -55,7 +55,7 @@ class TerminalInput(
for (event in bufferChannel) {
currentEvent = event
targetChannel.send(event)
val fusActivity = ReworkedTerminalUsageCollector.getFrontendTypingActivityOrNull(event)
val fusActivity = FrontendLatencyService.getInstance().getFrontendTypingActivityOrNull(event)
fusActivity?.reportDuration()
fusActivity?.finishTerminalInputEventProcessing()
currentEvent = null
@@ -72,7 +72,7 @@ class TerminalInput(
}
finally {
if (currentEvent != null) {
ReworkedTerminalUsageCollector.getFrontendTypingActivityOrNull(currentEvent)?.finishTerminalInputEventProcessing()
FrontendLatencyService.getInstance().getFrontendTypingActivityOrNull(currentEvent)?.finishTerminalInputEventProcessing()
}
}
}
@@ -96,7 +96,7 @@ class TerminalInput(
}
fun sendBytes(data: ByteArray) {
val fusActivity = ReworkedTerminalUsageCollector.getCurrentKeyEventTypingActivityOrNull()
val fusActivity = FrontendLatencyService.getInstance().getCurrentKeyEventTypingActivityOrNull()
val writeBytesEvent = TerminalWriteBytesEvent(bytes = data, id = fusActivity?.id)
fusActivity?.startTerminalInputEventProcessing(writeBytesEvent)
sendEvent(writeBytesEvent)
@@ -115,7 +115,7 @@ class TerminalInput(
}
private fun sendEvent(event: TerminalInputEvent) {
val fusActivity = ReworkedTerminalUsageCollector.getFrontendTypingActivityOrNull(event)
val fusActivity = FrontendLatencyService.getInstance().getFrontendTypingActivityOrNull(event)
val result = bufferChannel.trySend(event)
if (fusActivity != null && result.isFailure) {
fusActivity.finishTerminalInputEventProcessing()
@@ -0,0 +1,189 @@
package com.intellij.terminal.frontend.fus
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.platform.rpc.UID
import com.intellij.terminal.session.TerminalContentUpdatedEvent
import com.intellij.terminal.session.TerminalInputEvent
import com.intellij.terminal.session.TerminalSession
import com.intellij.terminal.session.TerminalWriteBytesEvent
import com.intellij.util.concurrency.ThreadingAssertions
import fleet.multiplatform.shims.ConcurrentHashMap
import org.jetbrains.plugins.terminal.block.reworked.session.FrontendTerminalSession
import org.jetbrains.plugins.terminal.fus.FrontendLatencyService
import org.jetbrains.plugins.terminal.fus.FrontendOutputActivity
import org.jetbrains.plugins.terminal.fus.FrontendTypingActivity
import org.jetbrains.plugins.terminal.fus.ReworkedTerminalUsageCollector
import java.awt.event.KeyEvent
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.CompletableFuture
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference
import kotlin.time.TimeMark
import kotlin.time.TimeSource
internal class FrontendLatencyServiceImpl : FrontendLatencyService {
override fun startFrontendTypingActivity(e: KeyEvent): FrontendTypingActivity? {
ThreadingAssertions.softAssertEventDispatchThread()
if (e.id != KeyEvent.KEY_TYPED) return null
val activity = FrontendTypingActivityImpl(frontendTypingActivityId.incrementAndGet())
currentKeyEventTypingActivity = activity
return activity
}
override fun getCurrentKeyEventTypingActivityOrNull(): FrontendTypingActivity? {
ThreadingAssertions.softAssertEventDispatchThread()
return currentKeyEventTypingActivity
}
override fun getFrontendTypingActivityOrNull(event: TerminalInputEvent): FrontendTypingActivity? {
return frontendTypingActivityByInputEvent[event.toIdentity()]
}
override fun startFrontendOutputActivity(
sessionFuture: CompletableFuture<TerminalSession>,
outputEditor: EditorImpl,
alternateBufferEditor: EditorImpl,
): FrontendOutputActivity {
return FrontendOutputActivityImpl(sessionFuture, outputEditor, alternateBufferEditor)
}
}
private val frontendTypingActivityId = AtomicInteger()
private var currentKeyEventTypingActivity: FrontendTypingActivityImpl? = null
private val frontendTypingActivityByInputEvent = ConcurrentHashMap<IdentityWrapper<TerminalInputEvent>, FrontendTypingActivityImpl>()
private class FrontendTypingActivityImpl(override val id: Int) : FrontendTypingActivity {
private val start = TimeSource.Monotonic.markNow()
private var writeBytesEvent: TerminalWriteBytesEvent? = null
override fun startTerminalInputEventProcessing(writeBytesEvent: TerminalWriteBytesEvent) {
this.writeBytesEvent = writeBytesEvent
frontendTypingActivityByInputEvent[writeBytesEvent.toIdentity()] = this
if (frontendTypingActivityByInputEvent.size > 10000) {
LOG.error(Throwable(
"Too many simultaneous frontend typing activities, likely a leak!" +
" Ensure that startTerminalInputEventProcessing() calls are paired with finishTerminalInputEventProcessing()"
))
}
}
override fun finishKeyEventProcessing() {
ThreadingAssertions.softAssertEventDispatchThread()
currentKeyEventTypingActivity = null
}
override fun reportDuration() {
val duration = start.elapsedNow()
ReworkedTerminalUsageCollector.logFrontendLatency(
inputEventId = id,
duration,
)
}
override fun finishTerminalInputEventProcessing() {
val inputEvent = writeBytesEvent
if (inputEvent != null) {
frontendTypingActivityByInputEvent.remove(inputEvent.toIdentity())
}
}
}
private class FrontendOutputActivityImpl(
sessionFuture: CompletableFuture<TerminalSession>,
private val outputEditor: EditorImpl,
private val alternateBufferEditor: EditorImpl,
) : FrontendOutputActivity {
private val sessionId = AtomicReference<UID?>()
private val pendingEvents = ArrayBlockingQueue<ReceivedEvent>(100)
private val pendingPaints = ArrayBlockingQueue<ReceivedEvent>(100)
private var editorRepaintRequests = 0L
private var editorRepaintRequestsBeforeModelUpdate = 0L
init {
sessionFuture.whenComplete { session, _ ->
sessionId.set((session as? FrontendTerminalSession?)?.id?.eid)
}
outputEditor.setRepaintCallback { editorRepaintRequested() }
alternateBufferEditor.setRepaintCallback { editorRepaintRequested() }
outputEditor.setPaintCallback { editorPainted() }
alternateBufferEditor.setPaintCallback { editorPainted() }
}
override fun eventReceived(event: TerminalContentUpdatedEvent) {
pendingEvents.addDroppingOldest(ReceivedEvent(TimeSource.Monotonic.markNow(), event))
}
override fun beforeModelUpdate() {
editorRepaintRequestsBeforeModelUpdate = editorRepaintRequests
}
private fun editorRepaintRequested() {
++editorRepaintRequests
}
override fun afterModelUpdate() {
val repaintRequested = editorRepaintRequests > editorRepaintRequestsBeforeModelUpdate
val editorShowing = outputEditor.component.isShowing || alternateBufferEditor.component.isShowing
if (!editorShowing) {
pendingPaints.clear() // editor no longer showing, so if there were unprocessed requests, they won't complete
}
val repaintExpected = repaintRequested && editorShowing
while (true) {
val pendingEvent = pendingEvents.poll() ?: break
if (repaintExpected) {
pendingPaints.addDroppingOldest(pendingEvent)
}
else {
reportLatency(pendingEvent, false)
}
}
}
private fun editorPainted() {
while (true) {
val pendingPaint = pendingPaints.poll() ?: break
reportLatency(pendingPaint, true)
}
}
private fun reportLatency(receivedEvent: ReceivedEvent, painted: Boolean) {
val latency = receivedEvent.time.elapsedNow()
val sessionId = this.sessionId.get()
if (sessionId == null) {
LOG.error("For some reason sessionId was not initialized, likely a bug")
return
}
ReworkedTerminalUsageCollector.logFrontendOutputLatency(
sessionId = sessionId,
firstCharIndex = receivedEvent.event.firstCharIndex,
lastCharIndex = receivedEvent.event.lastCharIndex,
duration = latency,
repainted = painted,
)
}
private data class ReceivedEvent(val time: TimeMark, val event: TerminalContentUpdatedEvent)
}
private fun <T> ArrayBlockingQueue<T>.addDroppingOldest(element: T) {
var overflow = false
while (!offer(element)) {
overflow = true
poll()
}
if (overflow) {
LOG.warn("Overflow in the frontend output activity queue, too many requests, maybe the queue is too small?")
}
}
// used to track individual instances of data classes
private class IdentityWrapper<T : Any>(private val instance: T) {
override fun equals(other: Any?): Boolean = instance === (other as? IdentityWrapper<T>)?.instance
override fun hashCode(): Int = System.identityHashCode(instance)
}
private fun <T : Any> T.toIdentity(): IdentityWrapper<T> = IdentityWrapper(this)
private val LOG = logger<FrontendLatencyService>()
@@ -10,6 +10,7 @@ import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.onEach
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalSessionApi
import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalSessionId
@@ -19,7 +20,8 @@ import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalSession
* Normally, it should be located in the frontend module, but it can't be moved there
* because it should be accessible from the shared terminal widget creating API with a lot of external usages.
*/
internal class FrontendTerminalSession(internal val id: TerminalSessionId) : TerminalSession {
@ApiStatus.Internal
class FrontendTerminalSession(val id: TerminalSessionId) : TerminalSession {
@Volatile
override var isClosed: Boolean = false
private set
@@ -0,0 +1,50 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.terminal.fus
import com.intellij.openapi.components.service
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.terminal.session.TerminalContentUpdatedEvent
import com.intellij.terminal.session.TerminalInputEvent
import com.intellij.terminal.session.TerminalSession
import com.intellij.terminal.session.TerminalWriteBytesEvent
import org.jetbrains.annotations.ApiStatus
import java.awt.event.KeyEvent
import java.util.concurrent.CompletableFuture
@ApiStatus.Internal
interface FrontendLatencyService {
companion object {
@JvmStatic
fun getInstance(): FrontendLatencyService = service()
}
fun startFrontendTypingActivity(e: KeyEvent): FrontendTypingActivity?
fun getCurrentKeyEventTypingActivityOrNull(): FrontendTypingActivity?
fun getFrontendTypingActivityOrNull(event: TerminalInputEvent): FrontendTypingActivity?
fun startFrontendOutputActivity(
sessionFuture: CompletableFuture<TerminalSession>,
outputEditor: EditorImpl,
alternateBufferEditor: EditorImpl,
): FrontendOutputActivity
}
@ApiStatus.Internal
interface FrontendTypingActivity {
val id: Int
fun startTerminalInputEventProcessing(writeBytesEvent: TerminalWriteBytesEvent)
fun finishKeyEventProcessing()
fun reportDuration()
fun finishTerminalInputEventProcessing()
}
@ApiStatus.Internal
interface FrontendOutputActivity {
fun eventReceived(event: TerminalContentUpdatedEvent)
fun beforeModelUpdate()
fun afterModelUpdate()
}
@@ -4,31 +4,15 @@ package org.jetbrains.plugins.terminal.fus
import com.intellij.internal.statistic.eventLog.EventLogGroup
import com.intellij.internal.statistic.eventLog.events.EventFields
import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Version
import com.intellij.platform.rpc.UID
import com.intellij.terminal.session.TerminalContentUpdatedEvent
import com.intellij.terminal.session.TerminalInputEvent
import com.intellij.terminal.session.TerminalSession
import com.intellij.terminal.session.TerminalWriteBytesEvent
import com.intellij.util.concurrency.ThreadingAssertions
import com.intellij.util.system.OS
import fleet.multiplatform.shims.ConcurrentHashMap
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.plugins.terminal.block.reworked.session.FrontendTerminalSession
import org.jetbrains.plugins.terminal.fus.TerminalShellInfoStatistics.KNOWN_SHELLS
import org.jetbrains.plugins.terminal.fus.TerminalShellInfoStatistics.getShellNameForStat
import java.awt.event.KeyEvent
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.CompletableFuture
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.time.TimeMark
import kotlin.time.TimeSource
private const val GROUP_ID = "terminal"
@@ -123,7 +107,8 @@ object ReworkedTerminalUsageCollector : CounterUsagesCollector() {
EXECUTION_TIME_FIELD with executionTime.inWholeMilliseconds)
}
internal fun logFrontendLatency(inputEventId: Int, duration: Duration) {
@ApiStatus.Internal
fun logFrontendLatency(inputEventId: Int, duration: Duration) {
frontendTypingLatencyEvent.log(
INPUT_EVENT_ID_FIELD with inputEventId,
DURATION_FIELD with duration
@@ -156,7 +141,8 @@ object ReworkedTerminalUsageCollector : CounterUsagesCollector() {
)
}
internal fun logFrontendOutputLatency(sessionId: UID, firstCharIndex: Long, lastCharIndex: Long, duration: Duration, repainted: Boolean) {
@ApiStatus.Internal
fun logFrontendOutputLatency(sessionId: UID, firstCharIndex: Long, lastCharIndex: Long, duration: Duration, repainted: Boolean) {
frontendOutputLatencyEvent.log(
SESSION_ID with sessionId,
FIRST_CHAR_INDEX with firstCharIndex,
@@ -165,185 +151,4 @@ object ReworkedTerminalUsageCollector : CounterUsagesCollector() {
REPAINTED_FIELD with repainted,
)
}
fun startFrontendTypingActivity(e: KeyEvent): FrontendTypingActivity? {
ThreadingAssertions.softAssertEventDispatchThread()
if (e.id != KeyEvent.KEY_TYPED) return null
val activity = FrontendTypingActivityImpl(frontendTypingActivityId.incrementAndGet())
currentKeyEventTypingActivity = activity
return activity
}
fun getCurrentKeyEventTypingActivityOrNull(): FrontendTypingActivity? {
ThreadingAssertions.softAssertEventDispatchThread()
return currentKeyEventTypingActivity
}
fun getFrontendTypingActivityOrNull(event: TerminalInputEvent): FrontendTypingActivity? {
return frontendTypingActivityByInputEvent[event.toIdentity()]
}
@JvmStatic
fun startFrontendOutputActivity(
sessionFuture: CompletableFuture<TerminalSession>,
outputEditor: EditorImpl,
alternateBufferEditor: EditorImpl,
): FrontendOutputActivity {
return FrontendOutputActivityImpl(sessionFuture, outputEditor, alternateBufferEditor)
}
}
@ApiStatus.Internal
interface FrontendTypingActivity {
val id: Int
fun startTerminalInputEventProcessing(writeBytesEvent: TerminalWriteBytesEvent)
fun finishKeyEventProcessing()
fun reportDuration()
fun finishTerminalInputEventProcessing()
}
@ApiStatus.Internal
interface FrontendOutputActivity {
fun eventReceived(event: TerminalContentUpdatedEvent)
fun beforeModelUpdate()
fun afterModelUpdate()
}
private val frontendTypingActivityId = AtomicInteger()
private var currentKeyEventTypingActivity: FrontendTypingActivityImpl? = null
private val frontendTypingActivityByInputEvent = ConcurrentHashMap<IdentityWrapper<TerminalInputEvent>, FrontendTypingActivityImpl>()
// used to track individual instances of data classes
private class IdentityWrapper<T : Any>(private val instance: T) {
override fun equals(other: Any?): Boolean = instance === (other as? IdentityWrapper<T>)?.instance
override fun hashCode(): Int = System.identityHashCode(instance)
}
private fun <T : Any> T.toIdentity(): IdentityWrapper<T> = IdentityWrapper(this)
private class FrontendTypingActivityImpl(override val id: Int) : FrontendTypingActivity {
private val start = TimeSource.Monotonic.markNow()
private var writeBytesEvent: TerminalWriteBytesEvent? = null
override fun startTerminalInputEventProcessing(writeBytesEvent: TerminalWriteBytesEvent) {
this.writeBytesEvent = writeBytesEvent
frontendTypingActivityByInputEvent[writeBytesEvent.toIdentity()] = this
if (frontendTypingActivityByInputEvent.size > 10000) {
LOG.error(Throwable(
"Too many simultaneous frontend typing activities, likely a leak!" +
" Ensure that startTerminalInputEventProcessing() calls are paired with finishTerminalInputEventProcessing()"
))
}
}
override fun finishKeyEventProcessing() {
ThreadingAssertions.softAssertEventDispatchThread()
currentKeyEventTypingActivity = null
}
override fun reportDuration() {
val duration = start.elapsedNow()
ReworkedTerminalUsageCollector.logFrontendLatency(
inputEventId = id,
duration,
)
}
override fun finishTerminalInputEventProcessing() {
val inputEvent = writeBytesEvent
if (inputEvent != null) {
frontendTypingActivityByInputEvent.remove(inputEvent.toIdentity())
}
}
}
private class FrontendOutputActivityImpl(
sessionFuture: CompletableFuture<TerminalSession>,
private val outputEditor: EditorImpl,
private val alternateBufferEditor: EditorImpl,
) : FrontendOutputActivity {
private val sessionId = AtomicReference<UID?>()
private val pendingEvents = ArrayBlockingQueue<ReceivedEvent>(100)
private val pendingPaints = ArrayBlockingQueue<ReceivedEvent>(100)
private var editorRepaintRequests = 0L
private var editorRepaintRequestsBeforeModelUpdate = 0L
init {
sessionFuture.whenComplete { session, _ ->
sessionId.set((session as? FrontendTerminalSession?)?.id?.uid)
}
outputEditor.setRepaintCallback { editorRepaintRequested() }
alternateBufferEditor.setRepaintCallback { editorRepaintRequested() }
outputEditor.setPaintCallback { editorPainted() }
alternateBufferEditor.setPaintCallback { editorPainted() }
}
override fun eventReceived(event: TerminalContentUpdatedEvent) {
pendingEvents.addDroppingOldest(ReceivedEvent(TimeSource.Monotonic.markNow(), event))
}
override fun beforeModelUpdate() {
editorRepaintRequestsBeforeModelUpdate = editorRepaintRequests
}
private fun editorRepaintRequested() {
++editorRepaintRequests
}
override fun afterModelUpdate() {
val repaintRequested = editorRepaintRequests > editorRepaintRequestsBeforeModelUpdate
val editorShowing = outputEditor.component.isShowing || alternateBufferEditor.component.isShowing
if (!editorShowing) {
pendingPaints.clear() // editor no longer showing, so if there were unprocessed requests, they won't complete
}
val repaintExpected = repaintRequested && editorShowing
while (true) {
val pendingEvent = pendingEvents.poll() ?: break
if (repaintExpected) {
pendingPaints.addDroppingOldest(pendingEvent)
}
else {
reportLatency(pendingEvent, false)
}
}
}
private fun editorPainted() {
while (true) {
val pendingPaint = pendingPaints.poll() ?: break
reportLatency(pendingPaint, true)
}
}
private fun reportLatency(receivedEvent: ReceivedEvent, painted: Boolean) {
val latency = receivedEvent.time.elapsedNow()
val sessionId = this.sessionId.get()
if (sessionId == null) {
LOG.error("For some reason sessionId was not initialized, likely a bug")
return
}
ReworkedTerminalUsageCollector.logFrontendOutputLatency(
sessionId = sessionId,
firstCharIndex = receivedEvent.event.firstCharIndex,
lastCharIndex = receivedEvent.event.lastCharIndex,
duration = latency,
repainted = painted,
)
}
private data class ReceivedEvent(val time: TimeMark, val event: TerminalContentUpdatedEvent)
}
private fun <T> ArrayBlockingQueue<T>.addDroppingOldest(element: T) {
var overflow = false
while (!offer(element)) {
overflow = true
poll()
}
if (overflow) {
LOG.warn("Overflow in the frontend output activity queue, too many requests, maybe the queue is too small?")
}
}
private val LOG = logger<FrontendTerminalSession>()