From 8fa4a1fc9a032fb1b10e5e2de9b9e545d7eca812 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Wed, 17 Jul 2024 16:01:08 +0200 Subject: [PATCH] IJPL-158385 ToolboxUpdateActions - use coroutine scope, avoid Disposable GitOrigin-RevId: a2e45785e0324d654c6f9747816ae2609d8fc171 --- .../built-in-server/api-dump-unreviewed.txt | 4 +- .../org/jetbrains/ide/ToolboxUpdateActions.kt | 39 +++++++---- platform/ide-core/api-dump-unreviewed.txt | 2 + .../util/ui/update/MergingUpdateQueue.kt | 64 ++++++++++++++++--- .../RecentProjectPanelComponentFactory.kt | 13 +++- 5 files changed, 96 insertions(+), 26 deletions(-) diff --git a/platform/built-in-server/api-dump-unreviewed.txt b/platform/built-in-server/api-dump-unreviewed.txt index 17989deebe77..1217c0247a4e 100644 --- a/platform/built-in-server/api-dump-unreviewed.txt +++ b/platform/built-in-server/api-dump-unreviewed.txt @@ -283,9 +283,7 @@ f:org.jetbrains.ide.ToolboxIdeExitHandler$ExitParameters f:org.jetbrains.ide.ToolboxRestServiceKt - sf:getToolboxHandlerEP():com.intellij.openapi.extensions.ExtensionPointName f:org.jetbrains.ide.ToolboxSettingsActionRegistry -- com.intellij.openapi.Disposable -- ():V -- dispose():V +- (kotlinx.coroutines.CoroutineScope):V - f:getActions():java.util.List - f:isNewAction(java.lang.String):Z - f:markAsRead(java.lang.String):V diff --git a/platform/built-in-server/src/org/jetbrains/ide/ToolboxUpdateActions.kt b/platform/built-in-server/src/org/jetbrains/ide/ToolboxUpdateActions.kt index 233e09e73889..36d082cd2501 100644 --- a/platform/built-in-server/src/org/jetbrains/ide/ToolboxUpdateActions.kt +++ b/platform/built-in-server/src/org/jetbrains/ide/ToolboxUpdateActions.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.ide import com.intellij.ide.actions.SettingsEntryPointAction @@ -7,24 +7,38 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.EDT import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.util.Disposer -import com.intellij.util.Alarm import com.intellij.util.messages.Topic -import com.intellij.util.ui.update.MergingUpdateQueue -import com.intellij.util.ui.update.Update +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.debounce import org.jetbrains.annotations.Nls import java.util.* +@OptIn(FlowPreview::class) @Service(Service.Level.APP) -class ToolboxSettingsActionRegistry : Disposable { +class ToolboxSettingsActionRegistry(coroutineScope: CoroutineScope) { private val readActions = Collections.synchronizedSet(HashSet()) private val pendingActions = Collections.synchronizedList(LinkedList()) - private val alarm = MergingUpdateQueue("toolbox-updates", 500, true, null, this, null, Alarm.ThreadToUse.SWING_THREAD).usePassThroughInUnitTestMode() + private val updateRequests = MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) - override fun dispose() = Unit + init { + coroutineScope.launch { + updateRequests + .debounce(500) + .collectLatest { + withContext(Dispatchers.EDT) { + SettingsEntryPointAction.updateState() + } + } + } + } fun isNewAction(actionId: String) = actionId !in readActions @@ -33,11 +47,12 @@ class ToolboxSettingsActionRegistry : Disposable { } fun scheduleUpdate() { - alarm.queue(object: Update(this){ - override fun run() { - SettingsEntryPointAction.updateState() - } - }) + if (ApplicationManager.getApplication().isUnitTestMode) { + SettingsEntryPointAction.updateState() + } + else { + check(updateRequests.tryEmit(Unit)) + } } internal fun registerUpdateAction(action: ToolboxUpdateAction) { diff --git a/platform/ide-core/api-dump-unreviewed.txt b/platform/ide-core/api-dump-unreviewed.txt index b63c674be327..ac5f07da6705 100644 --- a/platform/ide-core/api-dump-unreviewed.txt +++ b/platform/ide-core/api-dump-unreviewed.txt @@ -3196,6 +3196,8 @@ c:com.intellij.util.ui.update.MergingUpdateQueue - (java.lang.String,I,Z,javax.swing.JComponent,com.intellij.openapi.Disposable):V - (java.lang.String,I,Z,javax.swing.JComponent,com.intellij.openapi.Disposable,javax.swing.JComponent):V - (java.lang.String,I,Z,javax.swing.JComponent,com.intellij.openapi.Disposable,javax.swing.JComponent,com.intellij.util.Alarm$ThreadToUse):V +- (java.lang.String,I,Z,javax.swing.JComponent,com.intellij.openapi.Disposable,javax.swing.JComponent,com.intellij.util.Alarm$ThreadToUse,kotlinx.coroutines.CoroutineScope):V +- b:(java.lang.String,I,Z,javax.swing.JComponent,com.intellij.openapi.Disposable,javax.swing.JComponent,com.intellij.util.Alarm$ThreadToUse,kotlinx.coroutines.CoroutineScope,I,kotlin.jvm.internal.DefaultConstructorMarker):V - (java.lang.String,I,Z,javax.swing.JComponent,com.intellij.openapi.Disposable,javax.swing.JComponent,Z):V - b:(java.lang.String,I,Z,javax.swing.JComponent,com.intellij.openapi.Disposable,javax.swing.JComponent,Z,I,kotlin.jvm.internal.DefaultConstructorMarker):V - f:activate():V diff --git a/platform/ide-core/src/com/intellij/util/ui/update/MergingUpdateQueue.kt b/platform/ide-core/src/com/intellij/util/ui/update/MergingUpdateQueue.kt index 7d34b266be04..ec3d2c4ffac4 100644 --- a/platform/ide-core/src/com/intellij/util/ui/update/MergingUpdateQueue.kt +++ b/platform/ide-core/src/com/intellij/util/ui/update/MergingUpdateQueue.kt @@ -18,7 +18,7 @@ import com.intellij.util.Alarm import com.intellij.util.SystemProperties import com.intellij.util.ui.EdtInvocationManager import com.intellij.util.ui.update.UiNotifyConnector.Companion.installOn -import org.jetbrains.annotations.ApiStatus +import kotlinx.coroutines.CoroutineScope import org.jetbrains.annotations.ApiStatus.Internal import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.TestOnly @@ -50,14 +50,15 @@ import kotlin.concurrent.Volatile * @param activationComponent if not `null` the tasks will be processing only when the given component is showing * @param thread specifies on which thread the tasks are executed */ -open class MergingUpdateQueue( +open class MergingUpdateQueue @JvmOverloads constructor( private val name: @NonNls String, private var mergingTimeSpan: Int, isActive: Boolean, private var modalityStateComponent: JComponent?, parent: Disposable?, activationComponent: JComponent?, - thread: Alarm.ThreadToUse + thread: Alarm.ThreadToUse, + coroutineScope: CoroutineScope? = null, ) : Disposable, Activatable { @Volatile var isSuspended: Boolean = false @@ -111,6 +112,7 @@ open class MergingUpdateQueue( parent = parent, activationComponent = activationComponent, thread = if (executeInDispatchThread) Alarm.ThreadToUse.SWING_THREAD else Alarm.ThreadToUse.POOLED_THREAD, + coroutineScope = null, ) init { @@ -119,7 +121,17 @@ open class MergingUpdateQueue( Disposer.register(parent, this) } - waiterForMerge = if (executeInDispatchThread) Alarm(threadToUse = thread) else Alarm(threadToUse = thread, parentDisposable = this) + waiterForMerge = if (coroutineScope == null) { + if (executeInDispatchThread) { + Alarm(threadToUse = thread) + } + else { + Alarm(threadToUse = thread, parentDisposable = this) + } + } + else { + Alarm(threadToUse = thread, coroutineScope = coroutineScope) + } if (isActive) { showNotify() @@ -139,6 +151,42 @@ open class MergingUpdateQueue( @JvmField val ANY_COMPONENT: JComponent = object : JComponent() {} + @Internal + fun edtMergingUpdateQueue( + name: String, + mergingTimeSpan: Int, + coroutineScope: CoroutineScope, + ): MergingUpdateQueue { + return MergingUpdateQueue( + name = name, + mergingTimeSpan = mergingTimeSpan, + isActive = true, + modalityStateComponent = null, + parent = null, + activationComponent = null, + thread = Alarm.ThreadToUse.SWING_THREAD, + coroutineScope = coroutineScope, + ) + } + + @Internal + fun mergingUpdateQueue( + name: String, + mergingTimeSpan: Int, + coroutineScope: CoroutineScope, + ): MergingUpdateQueue { + return MergingUpdateQueue( + name = name, + mergingTimeSpan = mergingTimeSpan, + isActive = true, + modalityStateComponent = null, + parent = null, + activationComponent = null, + thread = Alarm.ThreadToUse.POOLED_THREAD, + coroutineScope = coroutineScope, + ) + } + private val queues: MutableSet? = if (SystemProperties.getBooleanProperty("intellij.MergingUpdateQueue.enable.global.flusher", false)) { ConcurrentCollectionFactory.createConcurrentSet() } @@ -146,7 +194,7 @@ open class MergingUpdateQueue( null } - @ApiStatus.Internal + @Internal fun flushAllQueues() { if (queues != null) { for (queue in queues) { @@ -185,7 +233,7 @@ open class MergingUpdateQueue( * It is needed to support some old tests, which expect such behaviour. * @return this instance for the sequential creation (the Builder pattern) */ - @ApiStatus.Internal + @Internal @Deprecated( """use {@link #waitForAllExecuted(long, TimeUnit)} instead in tests """) @@ -240,8 +288,8 @@ open class MergingUpdateQueue( restart(mergingTimeSpan) } - @ApiStatus.Internal - open protected fun getFlushTask(): Runnable = flushTask + @Internal + protected open fun getFlushTask(): Runnable = flushTask private fun restart(mergingTimeSpanMillis: Int) { if (!isActive) { diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/recentProjects/RecentProjectPanelComponentFactory.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/recentProjects/RecentProjectPanelComponentFactory.kt index 579c5ddb8523..260e9dd8cc07 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/recentProjects/RecentProjectPanelComponentFactory.kt +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/recentProjects/RecentProjectPanelComponentFactory.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.openapi.wm.impl.welcomeScreen.recentProjects import com.intellij.ide.RecentProjectsManager @@ -61,8 +61,15 @@ internal object RecentProjectPanelComponentFactory { } }) - val updateQueue = MergingUpdateQueue("Welcome screen UI updater", UPDATE_INTERVAL, true, null, - parentDisposable, tree, Alarm.ThreadToUse.SWING_THREAD) + val updateQueue = MergingUpdateQueue( + name = "Welcome screen UI updater", + mergingTimeSpan = UPDATE_INTERVAL, + isActive = true, + modalityStateComponent = null, + parent = parentDisposable, + activationComponent = tree, + thread = Alarm.ThreadToUse.SWING_THREAD, + ) updateQueue.queue(Update.create(filteringTree, Runnable { repaintProgressBars(updateQueue, filteringTree) })) return filteringTree