[rd debugger] IJPL-215857 Refactor: introduce XContainerId to unify computeChildren processing

IJ-CR-181826

GitOrigin-RevId: 8da907315334878315bae09eedc855b253e14610
This commit is contained in:
Maksim Zuev
2025-11-14 21:59:24 +00:00
committed by intellij-monorepo-bot
parent dfc9f40a52
commit 7d20442491
9 changed files with 52 additions and 97 deletions
@@ -62,11 +62,6 @@ internal class BackendXExecutionStackApi : XExecutionStackApi {
}.buffer(Channel.UNLIMITED)
}
override fun computeVariables(xStackFrameId: XStackFrameId): Flow<XValueComputeChildrenEvent> {
val stackFrameModel = xStackFrameId.findValue() ?: return emptyFlow()
return computeContainerChildren(stackFrameModel.coroutineScope, stackFrameModel.stackFrame, stackFrameModel.session)
}
override suspend fun canDrop(sessionId: XDebugSessionId, stackFrameId: XStackFrameId): Boolean {
val session = sessionId.findValue() ?: return false
val stack = stackFrameId.findValue() ?: return false
@@ -31,19 +31,13 @@ internal class BackendXValueApi : XValueApi {
return xValueModel.computeTooltipPresentation()
}
override fun computeChildren(xValueId: XValueId): Flow<XValueComputeChildrenEvent> {
val xValueModel = BackendXValueModel.findById(xValueId) ?: return emptyFlow()
return computeContainerChildren(xValueModel.cs, xValueModel.xValue, xValueModel.session)
}
override fun computeXValueGroupChildren(xValueGroupId: XValueGroupId): Flow<XValueComputeChildrenEvent> {
val xValueModel = xValueGroupId.findValue() ?: return emptyFlow()
return computeContainerChildren(xValueModel.cs, xValueModel.xValueGroup, xValueModel.session)
override fun computeChildren(id: XContainerId): Flow<XValueComputeChildrenEvent> {
return computeChildrenInternal(id)
}
override fun computeExpandedChildren(frameId: XStackFrameId, root: XDebuggerTreeExpandedNode): Flow<PreloadChildrenEvent> {
return channelFlow {
processExpandedChildren(XContainer.Frame(frameId), this, root)
processExpandedChildren(frameId, this, root)
}
}
@@ -177,7 +171,7 @@ private class AddNextChildrenCallbackHandler(cs: CoroutineScope) {
}
@Suppress("OPT_IN_USAGE")
internal fun computeContainerChildren(
private fun computeContainerChildren(
parentCs: CoroutineScope,
xValueContainer: XValueContainer,
session: XDebugSessionImpl,
@@ -326,48 +320,42 @@ private sealed interface RawComputeChildrenEvent {
}
}
private sealed interface XContainer {
fun createExpandEvent(event: XValueComputeChildrenEvent): PreloadChildrenEvent.ExpandedChildrenEvent
fun createPreloadEvent(): PreloadChildrenEvent.ToBePreloaded
suspend fun computeChildren(): Flow<XValueComputeChildrenEvent>
data class Value(val id: XValueId) : XContainer {
override fun createExpandEvent(event: XValueComputeChildrenEvent) = PreloadChildrenEvent.ExpandedChildrenEvent.Value(id, event)
override fun createPreloadEvent() = PreloadChildrenEvent.ToBePreloaded.Value(id)
override suspend fun computeChildren() = XValueApi.getInstance().computeChildren(id)
}
data class Group(val id: XValueGroupId) : XContainer {
override fun createExpandEvent(event: XValueComputeChildrenEvent) = PreloadChildrenEvent.ExpandedChildrenEvent.Group(id, event)
override fun createPreloadEvent() = PreloadChildrenEvent.ToBePreloaded.Group(id)
override suspend fun computeChildren() = XValueApi.getInstance().computeXValueGroupChildren(id)
}
data class Frame(val id: XStackFrameId) : XContainer {
override fun createExpandEvent(event: XValueComputeChildrenEvent) = PreloadChildrenEvent.ExpandedChildrenEvent.Frame(id, event)
override fun createPreloadEvent() = error("Frame preload event is implicit")
override suspend fun computeChildren() = XExecutionStackApi.getInstance().computeVariables(id)
private fun computeChildrenInternal(containerId: XContainerId): Flow<XValueComputeChildrenEvent> {
val (container, scope, session) = when (containerId) {
is XStackFrameId -> {
val stackFrameModel = containerId.findValue() ?: return emptyFlow()
Triple(stackFrameModel.stackFrame, stackFrameModel.coroutineScope, stackFrameModel.session)
}
is XValueGroupId -> {
val xGroupModel = containerId.findValue() ?: return emptyFlow()
Triple(xGroupModel.xValueGroup, xGroupModel.cs, xGroupModel.session)
}
is XValueId -> {
val xValueModel = BackendXValueModel.findById(containerId) ?: return emptyFlow()
Triple(xValueModel.xValue, xValueModel.cs, xValueModel.session)
}
}
return computeContainerChildren(scope, container, session)
}
/**
* Match [XContainer] children with [XDebuggerTreeExpandedNode] children, and start child computation if matched.
* Match [XContainerId] children with [XDebuggerTreeExpandedNode] children, and start child computation if matched.
*/
private suspend fun processExpandedChildren(id: XContainer, producerScope: ProducerScope<PreloadChildrenEvent>, root: XDebuggerTreeExpandedNode) {
private suspend fun processExpandedChildren(id: XContainerId, producerScope: ProducerScope<PreloadChildrenEvent>, root: XDebuggerTreeExpandedNode) {
val name2Child = MultiMap<String, XDebuggerTreeExpandedNode>()
for (child in root.children) {
name2Child.putValue(child.name, child)
}
val childrenEventsFlow = id.computeChildren()
val childrenEventsFlow = computeChildrenInternal(id)
childrenEventsFlow.collect { event ->
val childrenToLoad = collectChildrenToLoad(event, name2Child)
// notify which children are going to be preloaded
for ((childId, _) in childrenToLoad) {
producerScope.send(childId.createPreloadEvent())
producerScope.send(PreloadChildrenEvent.ToBePreloaded(childId))
}
// then pass the original event
producerScope.send(id.createExpandEvent(event))
producerScope.send(PreloadChildrenEvent.ExpandedChildrenEvent(id, event))
// and then start children loading
for ((childId, node) in childrenToLoad) {
producerScope.launch {
@@ -380,9 +368,9 @@ private suspend fun processExpandedChildren(id: XContainer, producerScope: Produ
private fun collectChildrenToLoad(
event: XValueComputeChildrenEvent,
name2Child: MultiMap<String, XDebuggerTreeExpandedNode>,
): List<Pair<XContainer, XDebuggerTreeExpandedNode>> {
): List<Pair<XContainerId, XDebuggerTreeExpandedNode>> {
if (event !is XValueComputeChildrenEvent.AddChildren) return emptyList()
val children = mutableListOf<Pair<XContainer, XDebuggerTreeExpandedNode>>()
val children = mutableListOf<Pair<XContainerId, XDebuggerTreeExpandedNode>>()
val namedContainers = collectNamedChildren(event)
for ((name, childId) in namedContainers) {
@@ -395,17 +383,17 @@ private fun collectChildrenToLoad(
return children
}
private fun collectNamedChildren(event: XValueComputeChildrenEvent.AddChildren): List<Pair<String, XContainer>> {
val nameAndId = mutableListOf<Pair<String, XContainer>>()
private fun collectNamedChildren(event: XValueComputeChildrenEvent.AddChildren): List<Pair<String, XContainerId>> {
val nameAndId = mutableListOf<Pair<String, XContainerId>>()
for (group in event.topGroups) {
nameAndId += group.groupName to XContainer.Group(group.id)
nameAndId += group.groupName to group.id
}
for (value in event.topValues + event.children) {
val name = value.name ?: continue
nameAndId += name to XContainer.Value(value.id)
nameAndId += name to value.id
}
for (group in event.bottomGroups) {
nameAndId += group.groupName to XContainer.Group(group.id)
nameAndId += group.groupName to group.id
}
return nameAndId
}
@@ -68,9 +68,7 @@ class FrontendXValue private constructor(
var canMarkValue: Boolean = false
private set
private val xValueContainer = FrontendXValueContainer(project, cs, hasParentValue, xValueDto.id) {
XValueApi.getInstance().computeChildren(xValueDto.id)
}
private val xValueContainer = FrontendXValueContainer(project, cs, hasParentValue, xValueDto.id)
private val fullValueEvaluator = fullValueEvaluatorFlow.map { evaluatorDto ->
if (evaluatorDto == null) {
@@ -5,10 +5,11 @@ import com.intellij.ide.ui.icons.icon
import com.intellij.openapi.application.EDT
import com.intellij.openapi.project.Project
import com.intellij.platform.debugger.impl.frontend.frame.PreloadManagerContainer
import com.intellij.platform.debugger.impl.rpc.XContainerId
import com.intellij.platform.debugger.impl.rpc.XValueApi
import com.intellij.platform.debugger.impl.rpc.XValueComputeChildrenEvent
import com.intellij.platform.debugger.impl.rpc.XValueGroupDto
import com.intellij.platform.debugger.impl.shared.XValuesPresentationBuilder
import com.intellij.platform.rpc.Id
import com.intellij.ui.SimpleTextAttributes
import com.intellij.xdebugger.frame.XCompositeNode
import com.intellij.xdebugger.frame.XNamedValue
@@ -16,20 +17,18 @@ import com.intellij.xdebugger.frame.XValueChildrenList
import com.intellij.xdebugger.frame.XValueContainer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
internal class FrontendXValueContainer(
private val project: Project,
private val cs: CoroutineScope,
private val hasParentValue: Boolean,
private val id: Id,
private val childrenComputation: suspend () -> Flow<XValueComputeChildrenEvent>,
private val id: XContainerId,
) : XValueContainer() {
override fun computeChildren(node: XCompositeNode) {
val preloadManager = cs.coroutineContext[PreloadManagerContainer]?.manager
node.childCoroutineScope(parentScope = cs, "FrontendXValueContainer#computeChildren").launch(Dispatchers.EDT) {
val flow = preloadManager?.getChildrenEventsFlow(id) ?: childrenComputation()
val flow = preloadManager?.getChildrenEventsFlow(id) ?: XValueApi.getInstance().computeChildren(id)
val builder = XValuesPresentationBuilder()
flow.collect { event ->
when (event) {
@@ -4,7 +4,6 @@ package com.intellij.platform.debugger.impl.frontend.evaluate.quick
import com.intellij.ide.ui.icons.icon
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.NlsSafe
import com.intellij.platform.debugger.impl.rpc.XValueApi
import com.intellij.platform.debugger.impl.rpc.XValueGroupDto
import com.intellij.xdebugger.frame.XCompositeNode
import com.intellij.xdebugger.frame.XValueGroup
@@ -18,9 +17,7 @@ internal class FrontendXValueGroup(
hasParentValue: Boolean,
) : XValueGroup(xValueGroupDto.groupName) {
private val xValueContainer = FrontendXValueContainer(project, cs, hasParentValue, xValueGroupDto.id) {
XValueApi.getInstance().computeXValueGroupChildren(xValueGroupDto.id)
}
private val xValueContainer = FrontendXValueContainer(project, cs, hasParentValue, xValueGroupDto.id)
override fun computeChildren(node: XCompositeNode) {
xValueContainer.computeChildren(node)
@@ -43,9 +43,7 @@ internal class FrontendXStackFrame(
createFrontendXDebuggerEvaluator(project, suspendContextLifetimeScope, evaluatorDto, id)
}
private val xValueContainer = FrontendXValueContainer(project, cs, false, id) {
XExecutionStackApi.getInstance().computeVariables(id)
}
private val xValueContainer = FrontendXValueContainer(project, cs, false, id)
val backgroundColor: Color?
get() = bgColor?.colorId?.color()
@@ -3,11 +3,9 @@ package com.intellij.platform.debugger.impl.frontend.frame
import com.intellij.openapi.diagnostic.fileLogger
import com.intellij.platform.debugger.impl.rpc.*
import com.intellij.platform.rpc.Id
import com.intellij.platform.util.coroutines.childScope
import com.intellij.util.AwaitCancellationAndInvoke
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeState
import fleet.multiplatform.shims.ConcurrentHashMap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
@@ -17,6 +15,7 @@ import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.launch
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.VisibleForTesting
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicReference
import kotlin.coroutines.AbstractCoroutineContextElement
import kotlin.coroutines.CoroutineContext
@@ -50,7 +49,7 @@ class VariablesPreloadManager(
frameId: XStackFrameId,
) {
private val cs = parentScope.childScope("VariablesPreloadManager")
private val preloadedEvents = ConcurrentHashMap<Id, Channel<XValueComputeChildrenEvent>>()
private val preloadedEvents = ConcurrentHashMap<XContainerId, Channel<XValueComputeChildrenEvent>>()
init {
markToBeLoaded(frameId)
@@ -72,14 +71,14 @@ class VariablesPreloadManager(
}
}
fun getChildrenEventsFlow(entityId: Id): Flow<XValueComputeChildrenEvent>? {
fun getChildrenEventsFlow(entityId: XContainerId): Flow<XValueComputeChildrenEvent>? {
val eventsChannel = preloadedEvents[entityId] ?: return null
return channelFlow {
eventsChannel.consumeEach { send(it) }
}
}
private fun markToBeLoaded(id: Id) {
private fun markToBeLoaded(id: XContainerId) {
val old = preloadedEvents.put(id, Channel(capacity = Channel.UNLIMITED))
assert(old == null) { "Channel for $id was already registered" }
}
@@ -17,8 +17,6 @@ interface XExecutionStackApi : RemoteApi<Unit> {
suspend fun computeStackFrames(executionStackId: XExecutionStackId, firstFrameIndex: Int): Flow<XStackFramesEvent>
fun computeVariables(xStackFrameId: XStackFrameId): Flow<XValueComputeChildrenEvent>
suspend fun canDrop(sessionId: XDebugSessionId, stackFrameId: XStackFrameId): Boolean
suspend fun dropFrame(sessionId: XDebugSessionId, stackFrameId: XStackFrameId)
@@ -42,7 +40,7 @@ data class XExecutionStackId(override val uid: UID) : Id
*/
@ApiStatus.Internal
@Serializable
data class XStackFrameId(override val uid: UID) : Id
data class XStackFrameId(override val uid: UID) : XContainerId
@ApiStatus.Internal
@Serializable
@@ -18,8 +18,7 @@ import org.jetbrains.annotations.ApiStatus
interface XValueApi : RemoteApi<Unit> {
suspend fun computeTooltipPresentation(xValueId: XValueId): Flow<XValueSerializedPresentation>
fun computeChildren(xValueId: XValueId): Flow<XValueComputeChildrenEvent>
fun computeXValueGroupChildren(xValueGroupId: XValueGroupId): Flow<XValueComputeChildrenEvent>
fun computeChildren(id: XContainerId): Flow<XValueComputeChildrenEvent>
fun computeExpandedChildren(frameId: XStackFrameId, root: XDebuggerTreeExpandedNode): Flow<PreloadChildrenEvent>
suspend fun disposeXValue(xValueId: XValueId)
@@ -40,17 +39,21 @@ interface XValueApi : RemoteApi<Unit> {
}
}
@ApiStatus.Internal
@Serializable
sealed interface XContainerId : Id
/**
* @see com.intellij.xdebugger.impl.rpc.models.BackendXValueModel
*/
@ApiStatus.Internal
@Serializable
data class XValueId(override val uid: UID) : Id
data class XValueId(override val uid: UID) : XContainerId
/** @see com.intellij.xdebugger.impl.rpc.models.BackendXValueGroupModel */
@ApiStatus.Internal
@Serializable
data class XValueGroupId(override val uid: UID) : Id
data class XValueGroupId(override val uid: UID) : XContainerId
@ApiStatus.Internal
@Serializable
@@ -70,28 +73,8 @@ data class XDebuggerTreeExpandedNode(
@Serializable
sealed interface PreloadChildrenEvent {
@Serializable
sealed interface ToBePreloaded : PreloadChildrenEvent {
val id: Id
@Serializable
data class Value(override val id: XValueId) : ToBePreloaded
@Serializable
data class Group(override val id: XValueGroupId) : ToBePreloaded
}
data class ToBePreloaded(val id: XContainerId) : PreloadChildrenEvent
@Serializable
sealed interface ExpandedChildrenEvent : PreloadChildrenEvent {
val id: Id
val event: XValueComputeChildrenEvent
@Serializable
data class Frame(override val id: XStackFrameId, override val event: XValueComputeChildrenEvent) : ExpandedChildrenEvent
@Serializable
data class Value(override val id: XValueId, override val event: XValueComputeChildrenEvent) : ExpandedChildrenEvent
@Serializable
data class Group(override val id: XValueGroupId, override val event: XValueComputeChildrenEvent) : ExpandedChildrenEvent
}
data class ExpandedChildrenEvent(val id: XContainerId, val event: XValueComputeChildrenEvent) : PreloadChildrenEvent
}