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
- Klasa przechowująca kulki BallContainer
- Klasa symulująca ich ruch i zderzenia Engine
- Klasa wyświetlająca kulki SimulationPanel2d
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ż?
- Dlatego nie powinno się używać samych (gołych) wątków...
-
Poprawna implementacja animacji z użyciem wątków.
Model Pamięci Javy
Klasa Swing Worker
- Klasa SwingWorker posiada dwie metody które użytkownik może nadpisać.
- doInBackground jest wykonywana w tle.
- done jest wykonywana w wątku EDT
- Przykład wykonania (animacja)
- Tutorial do używania SwingWorkerów
Thats all
Animacje w Javie
By Jacek Bzdak
Animacje w Javie
- 3,918