<commit_before>// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ui;

import com.intellij.openapi.application.ex.ApplicationInfoEx;
import com.intellij.openapi.application.ex.ProgressSlide;
import com.intellij.ui.scale.ScaleContext;
import com.intellij.util.ImageLoader;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.ui.StartupUiUtil;
import org.jetbrains.annotations.NotNull;

import java.awt.*;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

final class ProgressSlidePainter {
  private static final int PREFETCH_BUFFER_SIZE = 5;
  private final BlockingQueue<Slide> myPrefetchQueue = new ArrayBlockingQueue<>(PREFETCH_BUFFER_SIZE);
  private boolean isFinish;
  private Slide nextSlide = null;
  private final List<ProgressSlide> myProgressSlides;

  private static class Slide {
    public final double progress;
    public final Image image;
    public final boolean isLastSlide;

    Slide(double progress, Image image, boolean isLastSlide) {
      this.progress = progress;
      this.image = image;
      this.isLastSlide = isLastSlide;
    }
  }

  ProgressSlidePainter(@NotNull ApplicationInfoEx appInfo) {
    myProgressSlides = appInfo.getProgressSlides();
    myProgressSlides.sort(Comparator.comparing(it -> it.progressRation));
  }

  public void startPreloading() {
    AppExecutorUtil.getAppExecutorService().execute(() -> {
      for (int i = 0; i < myProgressSlides.size(); i++) {
        ProgressSlide slide = myProgressSlides.get(i);
        try {
          Image image = ImageLoader.loadFromUrl(slide.url, Splash.class, ImageLoader.ALLOW_FLOAT_SCALING, null, ScaleContext.create());
          if (image == null) {
            throw new IllegalStateException("Cannot load slide by url: " + slide.url);
          }
          myPrefetchQueue.put(new Slide(slide.progressRation, image, i == myProgressSlides.size() - 1));
        }
        catch (InterruptedException e) {
          return;
        }
      }
    });
  }

  public void paintSlides(@NotNull Graphics g, double currentProgress) {
    if (isFinish || (nextSlide != null && nextSlide.progress > currentProgress)) {
      return;
    }

    if (nextSlide != null) {
      StartupUiUtil.drawImage(g, nextSlide.image, 0, 0, null);
      if (nextSlide.isLastSlide) {
        isFinish = true;
        nextSlide = null;
        return;
      }
    }

    Slide newSlide;
    do {
      try {
        newSlide = myPrefetchQueue.take();
      }
      catch (InterruptedException e) {
        return;
      }
      if (newSlide.progress <= currentProgress) {
        StartupUiUtil.drawImage(g, newSlide.image, 0, 0, null);
        if (newSlide.isLastSlide) {
          nextSlide = null;
          isFinish = true;
        }
      }
      else {
        nextSlide = newSlide;
        break;
      }
    }
    while (!newSlide.isLastSlide);
  }
}<commit_msg>Improve splash preloading performance (~ 2 sec)<commit_after>// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ui;

import com.intellij.openapi.application.ex.ApplicationInfoEx;
import com.intellij.openapi.application.ex.ProgressSlide;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.ui.scale.ScaleContext;
import com.intellij.util.ImageLoader;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.ui.StartupUiUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.awt.*;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceArray;

final class ProgressSlidePainter {
  private static final int PREFETCH_PARALLEL_COUNT = 5;
  private static final Logger ourLogger = Logger.getInstance(ProgressSlidePainter.class);

  private final List<ProgressSlide> myProgressSlides;

  private final AtomicReferenceArray<Slide> myPrefetchedSlides;
  private final AtomicInteger myPrefetchSlideIndex;
  private int myNextSlideIndex = 0;

  private static class Slide {
    private static final Slide Empty = new Slide(0, null);

    public final double progress;
    public final Image image;

    public boolean isEmpty() {
      return Empty.equals(this);
    }

    Slide(double progress, Image image) {
      this.progress = progress;
      this.image = image;
    }
  }

  ProgressSlidePainter(@NotNull ApplicationInfoEx appInfo) {
    myProgressSlides = appInfo.getProgressSlides();
    myProgressSlides.sort(Comparator.comparing(it -> it.progressRation));
    myPrefetchedSlides = new AtomicReferenceArray<>(myProgressSlides.size());
    myPrefetchSlideIndex = new AtomicInteger(0);
  }

  private boolean isFinish() {
    return myNextSlideIndex >= myPrefetchedSlides.length();
  }

  public void startPreloading() {
    var executorService = AppExecutorUtil.getAppExecutorService();
    for (int i = 0; i < PREFETCH_PARALLEL_COUNT; i++) {
      executorService.execute(() -> {
        while (true) {
          int slideIndex = myPrefetchSlideIndex.getAndIncrement();
          if (slideIndex >= myProgressSlides.size()) return;

          if (slideIndex < myNextSlideIndex)
          {
            // current slide will not be shown
            myPrefetchedSlides.set(slideIndex, Slide.Empty);
            continue;
          }

          var slide = myProgressSlides.get(slideIndex);
          var image = ImageLoader.loadFromUrl(slide.url, Splash.class, ImageLoader.ALLOW_FLOAT_SCALING, null, ScaleContext.create());

          if (image == null) {
            ourLogger.error("Cannot load slide by url: " + slide.url);
            myPrefetchedSlides.set(slideIndex, Slide.Empty);
            continue;
          }

          myPrefetchedSlides.compareAndSet(slideIndex, null, new Slide(slide.progressRation, image));
        }
      });
    }
  }

  public void paintSlides(@NotNull Graphics g, double currentProgress) {
    if (isFinish()) return;

    do {
      var newSlide = tryGetNextSlide();
      if (newSlide == null) return;

      if (newSlide.isEmpty())
        return;

      if (newSlide.progress <= currentProgress) {
        StartupUiUtil.drawImage(g, newSlide.image, 0, 0, null);
      } else return;
    }
    while (true);
  }

  // on ui thread
  @Nullable
  private Slide tryGetNextSlide() {
    int index = myNextSlideIndex;
    if (index >= myPrefetchedSlides.length()) return null;

    var start = System.currentTimeMillis();
    do {
      var slide = myPrefetchedSlides.get(index);
      if (slide != null) {
        myPrefetchedSlides.set(index, Slide.Empty);
        myNextSlideIndex++;
        return slide;
      } else if (System.currentTimeMillis() - start > 10) {
        ourLogger.warn("Cannot get the next slide for 10 ms");
        myPrefetchedSlides.set(index, Slide.Empty);
        myNextSlideIndex++;
        return Slide.Empty;
      }

      Thread.onSpinWait();
    } while (true);
  }
}
{'fraction_non_alphanumeric': 0.09941682865870592, 'fraction_numerical': 0.005415162454873646, 'mean_word_length': 3.1019362186788153, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 18, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3020938', 'n_tokens_mistral': 2244, 'n_tokens_neox': 2163, 'n_words': 503}