mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
git: reduce blinking when loading annotations
* avoid unnecessary update in `invokeLater` if file history can be computed fast enough
This commit is contained in:
+110
-38
@@ -23,10 +23,12 @@ import com.intellij.openapi.progress.EmptyProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.util.Computable;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.util.Consumer;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.PairConsumer;
|
||||
import org.jetbrains.annotations.CalledInAny;
|
||||
import org.jetbrains.annotations.CalledInAwt;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -39,18 +41,24 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
public class BackgroundTaskUtil {
|
||||
private static final Logger LOG = Logger.getInstance(BackgroundTaskUtil.class);
|
||||
|
||||
/*
|
||||
* Executor to perform <possibly> long operations on pooled thread
|
||||
* It can be used to reduce blinking if background task completed fast. In this case callback will be called without invokeLater().
|
||||
*
|
||||
* Simple approach:
|
||||
*
|
||||
/**
|
||||
* Executor to perform <i>possibly</i> long operation on pooled thread.
|
||||
* If computation was performed within given time frame,
|
||||
* the computed callback will be executed synchronously (avoiding unnecessary <tt>invokeLater()</tt>).
|
||||
* In this case, {@code onSlowAction} will not be executed at all.
|
||||
* <ul>
|
||||
* <li> If the computation is fast, execute callback synchronously.
|
||||
* <li> If the computation is slow, execute <tt>onSlowAction</tt> synchronously. When the computation is completed, execute callback in EDT.
|
||||
* </ul><p>
|
||||
* It can be used to reduce blinking when background task might be completed fast.<br>
|
||||
* A Simple approach:
|
||||
* <pre>
|
||||
* onSlowAction.run() // show "Loading..."
|
||||
* executeOnPooledThread({
|
||||
* Runnable callback = backgroundTask(); // some background computations
|
||||
* invokeLater(callback); // apply changes
|
||||
* });
|
||||
*
|
||||
* Runnable callback = backgroundTask(); // some background computations
|
||||
* invokeLater(callback); // apply changes
|
||||
* });
|
||||
* </pre>
|
||||
* will lead to "Loading..." visible between current moment and execution of invokeLater() event.
|
||||
* This period can be very short and looks like 'jumping' if background operation is fast.
|
||||
*/
|
||||
@@ -69,9 +77,9 @@ public class BackgroundTaskUtil {
|
||||
int waitMillis,
|
||||
boolean forceEDT) {
|
||||
ModalityState modality = ModalityState.current();
|
||||
ProgressIndicator indicator = new EmptyProgressIndicator(modality);
|
||||
|
||||
if (forceEDT) {
|
||||
ProgressIndicator indicator = new EmptyProgressIndicator(modality);
|
||||
try {
|
||||
Runnable callback = backgroundTask.fun(indicator);
|
||||
finish(callback, indicator);
|
||||
@@ -81,52 +89,116 @@ public class BackgroundTaskUtil {
|
||||
catch (Throwable t) {
|
||||
LOG.error(t);
|
||||
}
|
||||
return indicator;
|
||||
}
|
||||
else {
|
||||
Helper<Runnable> helper = new Helper<>();
|
||||
Pair<Runnable, ProgressIndicator> pair = computeInBackgroundAndTryWait(
|
||||
backgroundTask,
|
||||
(callback, indicator) -> {
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
finish(callback, indicator);
|
||||
}, modality);
|
||||
},
|
||||
modality,
|
||||
waitMillis);
|
||||
|
||||
ApplicationManager.getApplication().executeOnPooledThread(() -> {
|
||||
ProgressManager.getInstance().executeProcessUnderProgress(() -> {
|
||||
Runnable callback = backgroundTask.fun(indicator);
|
||||
Runnable callback = pair.first;
|
||||
ProgressIndicator indicator = pair.second;
|
||||
|
||||
if (!helper.setResult(callback)) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
finish(callback, indicator);
|
||||
}, modality);
|
||||
}
|
||||
}, indicator);
|
||||
});
|
||||
|
||||
if (helper.await(waitMillis)) {
|
||||
finish(helper.getResult(), indicator);
|
||||
if (callback != null) {
|
||||
finish(callback, indicator);
|
||||
}
|
||||
else {
|
||||
if (onSlowAction != null) onSlowAction.run();
|
||||
}
|
||||
}
|
||||
|
||||
return indicator;
|
||||
return indicator;
|
||||
}
|
||||
}
|
||||
|
||||
@CalledInAwt
|
||||
private static void finish(@NotNull Runnable result, @NotNull ProgressIndicator indicator) {
|
||||
if (indicator.isCanceled()) return;
|
||||
result.run();
|
||||
indicator.stop();
|
||||
if (!indicator.isCanceled()) result.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to compute value in background and abort computation if it takes too long.
|
||||
* <ul>
|
||||
* <li> If the computation is fast, return computed value.
|
||||
* <li> If the computation is slow, abort computation (cancel ProgressIndicator).
|
||||
* </ul>
|
||||
*/
|
||||
@Nullable
|
||||
@CalledInAwt
|
||||
public static <T> T tryComputeFast(@NotNull Function<ProgressIndicator, T> backgroundTask,
|
||||
int waitMillis) {
|
||||
Ref<T> resultRef = new Ref<>();
|
||||
ProgressIndicator indicator = executeAndTryWait(indicator1 -> {
|
||||
T result = backgroundTask.fun(indicator1);
|
||||
return () -> resultRef.set(result);
|
||||
}, null, waitMillis, false);
|
||||
indicator.cancel();
|
||||
Pair<T, ProgressIndicator> pair = computeInBackgroundAndTryWait(
|
||||
backgroundTask,
|
||||
(result, indicator) -> {
|
||||
},
|
||||
ModalityState.defaultModalityState(),
|
||||
waitMillis);
|
||||
|
||||
return resultRef.get();
|
||||
T result = pair.first;
|
||||
ProgressIndicator indicator = pair.second;
|
||||
|
||||
indicator.cancel();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@CalledInAny
|
||||
public static <T> T computeInBackgroundAndTryWait(@NotNull Computable<T> computable,
|
||||
@NotNull Consumer<T> asyncCallback,
|
||||
int waitMillis) {
|
||||
Pair<T, ProgressIndicator> pair = computeInBackgroundAndTryWait(
|
||||
indicator -> computable.compute(),
|
||||
(result, indicator) -> asyncCallback.consume(result),
|
||||
ModalityState.defaultModalityState(),
|
||||
waitMillis
|
||||
);
|
||||
return pair.first;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute value in background and try wait for its completion.
|
||||
* <ul>
|
||||
* <li> If the computation is fast, return computed value synchronously. Callback will not be called in this case.
|
||||
* <li> If the computation is slow, return <tt>null</tt>. When the computation is completed, pass the value to the callback.
|
||||
* </ul>
|
||||
* Callback will be executed on the same thread as the background task.
|
||||
*/
|
||||
@NotNull
|
||||
@CalledInAny
|
||||
public static <T> Pair<T, ProgressIndicator> computeInBackgroundAndTryWait(@NotNull Function<ProgressIndicator, T> task,
|
||||
@NotNull PairConsumer<T, ProgressIndicator> asyncCallback,
|
||||
@NotNull ModalityState modality,
|
||||
int waitMillis) {
|
||||
ProgressIndicator indicator = new EmptyProgressIndicator(modality);
|
||||
|
||||
Helper<T> helper = new Helper<>();
|
||||
|
||||
indicator.start();
|
||||
ApplicationManager.getApplication().executeOnPooledThread(() -> {
|
||||
ProgressManager.getInstance().executeProcessUnderProgress(() -> {
|
||||
try {
|
||||
T result = task.fun(indicator);
|
||||
if (!helper.setResult(result)) {
|
||||
asyncCallback.consume(result, indicator);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
indicator.stop();
|
||||
}
|
||||
}, indicator);
|
||||
});
|
||||
|
||||
T result = null;
|
||||
if (helper.await(waitMillis)) {
|
||||
result = helper.getResult();
|
||||
}
|
||||
|
||||
return Pair.create(result, indicator);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import com.intellij.openapi.diagnostic.Attachment;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.progress.util.BackgroundTaskUtil;
|
||||
import com.intellij.openapi.progress.util.ProgressWindow;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vcs.FilePath;
|
||||
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
|
||||
@@ -173,30 +175,38 @@ public class GitAnnotationProvider implements AnnotationProviderEx {
|
||||
}
|
||||
|
||||
private void loadFileHistoryInBackground(@NotNull GitFileAnnotation fileAnnotation) {
|
||||
ApplicationManager.getApplication().executeOnPooledThread(() -> {
|
||||
try {
|
||||
VirtualFile file = fileAnnotation.getFile();
|
||||
FilePath filePath = VcsUtil.getFilePath(file);
|
||||
VcsRevisionNumber currentRevision = fileAnnotation.getCurrentRevision();
|
||||
List<VcsFileRevision> fileRevisions = BackgroundTaskUtil.computeInBackgroundAndTryWait(
|
||||
() -> {
|
||||
try {
|
||||
VirtualFile file = fileAnnotation.getFile();
|
||||
FilePath filePath = VcsUtil.getFilePath(file);
|
||||
VcsRevisionNumber currentRevision = fileAnnotation.getCurrentRevision();
|
||||
|
||||
List<VcsFileRevision> revisions;
|
||||
if (file.isInLocalFileSystem() || currentRevision == null) {
|
||||
revisions = loadFileHistory(filePath);
|
||||
if (file.isInLocalFileSystem() || currentRevision == null) {
|
||||
return loadFileHistory(filePath);
|
||||
}
|
||||
else {
|
||||
return GitHistoryUtils.history(myProject, filePath, null, currentRevision);
|
||||
}
|
||||
}
|
||||
else {
|
||||
revisions = GitHistoryUtils.history(myProject, filePath, null, currentRevision);
|
||||
catch (VcsException e) {
|
||||
LOG.error(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
},
|
||||
(revisions) -> {
|
||||
if (revisions == null) return;
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
fileAnnotation.setRevisions(revisions);
|
||||
fileAnnotation.reload();
|
||||
});
|
||||
}
|
||||
catch (VcsException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS
|
||||
);
|
||||
|
||||
if (fileRevisions != null) {
|
||||
fileAnnotation.setRevisions(fileRevisions);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
Reference in New Issue
Block a user