Animacje w Javie

Problemy, rozwiązania, recepty


Jacek Bzdak

Kod przykładowy


Przykładowy kod dostępny jest na githubie:

Przykładowy projekt

Struktura przykładowego projektu



Wyświetlanie kulek


Komponent wyświetlający kulki wyświetla je (nie martwiąc się symulacją!)


@Override
protected void paintComponent(Graphics g) {
    setForeground(Color.BLUE);
    Graphics2D g2 = (Graphics2D)g;

    g2.clearRect(0, 0, getWidth(), getHeight());
    
    for(Ball b: engine.getBalls()){
        printBall(g2, b);
    }
    
}
 

Wyświetlanie kulki



g.fillOval((int) x, (int) y, (int) radiusX*2, (int) radiusY*2); 

Oba przykłady są uproszczone (proszę spojrzeć na oryginal).

Symulacja


Kod z symulacją robi symulację:

 
public void iterate(BallContainer ballContainer, double dt){

    List<Ball> balls = ballContainer.getBalls();
    
    for (int ii=0;ii<balls.size(); ii++){
        Ball b1 = balls.get(ii);
        checkCollisions(ballContainer, b1, ii);
        b1.iteration(dt);
    }
}    
    
    



Uwaga implementacyjna


  • W tym projekcie są cztery metody wykonania wielowątkowości.
  • By łatwo przełączać się między nimi stworzyłem interfejs: AnimationProvider
    • Jego instancje zawierają instancje klas:
      • Engine
      • SimulationPanel2D
      • BallContainer
    • Oraz metody
      • start
      • stop
    • I odpowiadają za wykonanie animacji

Animacja za pomocą timera

Stworzenie obiektu timer


timer = new Timer(100, new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        if (engine != null){
            engine.iterate(ballContainer, 0.05);
            simulationPanel2D.repaint();
        }
    }
});        
timer.start();

Timer cont.


  • Co 100ms
  • Jedna interacja obliczeń
  • Wyświetlenie obliczeń

Zalety

  •  Łatwa do wykonania metoda


Wady

  • Obliczenia w wątku GUI

Wątek EDT


  •  Wszystkie
    • operacje na komponentach graficznych 
    • zdarzenia (kliknięcia, ...)
  • Są przetwarzane w wątku EDT
  • Jeśli jedna z tych czynności będzie trwała długo cały interfejs graficzny przestanie odpowiadać na kliknięcia.

Co chcemy uzyskać


  • Chcemy przenieść obliczenia do innego wątku, tak by wątek GUI tylko wyświetlał ich wyniki. 

Wielowątkowość

Wątek


  • Wątek sekwencja poleceń wyonywanych w jednym ciągu
  • Maszyna Wirtualna JAVA gwarantuje że z puntku widzenia użytkownika poszczególne polecenia (linie kodu) są w ramach wątku wykonanywane w takiej kolejości w jakiej są w kodzie źródłowym
  • W przypadku programów wielowątkowych  takich gwarancji nie ma


Błędna implemetacja na wątkach


animation @Override
    public void start() {
        super.start();
        isStarted = true;

        animationThread = new Thread(){
            @Override
            public void run() {
                while (true){
                    if (!isStarted){
                        return;
                    }
                    engine.iterate(ballContainer, 0.01);
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        //noop
                    }
                }
            }
        };

        timer.start();
        animationThread.start();
    }

    @Override
    public void stop() {
        super.stop();
        try {
            animationThread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return;
        }
        timer.stop();
        isStarted = false;

    }    
    

Intencja programisty


  • Wątek działa póki flaga isStarted nie zostanie ustawiona na false, po tym umiera sam. 
while (true){
    if (!isStarted){
        return;
    }
  • W metodzie stop, ustawiamy flagę i czekamy aż wątek się nie wyłączy
isStarted = false;animationThread.join();

Dlaczego może nie działać?

Maszyna wirtualna może dokonać optymalizacji która wyrzuca sprawdzenie poza pętlę while (pętla ta nie zmienia stanu isStarted), więc z punktu widzenia wątku optymalizacja ta jest legalna.


Synchronizacja pamięci między wątkami.


  • Wątek A widzi zmiany w zmiennej x wykonane przez wątek B jeśli.
  • Zapis nastąpił przed odczytem
  • Zapis w wątku B został wykonany w po uzyskaniu Locka L
  • Odczyt w wątku A został wykonany po uzyskaniu Locka L

Pojęcie Locka

  • Lock jest obiektem który może być posiadany przez jeden wątek na raz.
  • By uzyskać Locka należy wywołać metodę Lock.acquire
  • By zwolnić kontrolę nad Lockiem należy wykonać Lock.unlock

threadLock.lock();
try{
    engine.iterate(ballContainer, 0.01);
} finally {
    threadLock.unlock();
}    
    


Gwarancje dawane przez locka


  • Lock powoduje synchronizację pamięci między wątkami
  • JVM nie może przenosić instrukcji między granicami obszaru chronoionego przez locka.
  • Lock jest obiektem który może być posiadany przez jeden wątek na raz.

Skoplikowane nieprawdaż?


Model Pamięci Javy

Klasa Swing Worker


Thats all

Animacje w Javie

By Jacek Bzdak

Animacje w Javie

  • 3,918