/* * Engine Alpha ist eine anfängerorientierte 2D-Gaming Engine. * * Copyright (c) 2011 - 2017 Michael Andonie and contributors. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ea; import ea.internal.util.Logger; import java.awt.*; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** * <p>Der Manager ist eine Standardklasse und eine der wichtigsten der Engine Alpha, die zur * Interaktion außerhalb der Engine benutzt werden kann.</p> * <p>Neben einer Liste aller möglichen Fonts handelt er auch das <b>Ticker-System</b>. Dies ist ein * relativ konsistentes System, das viele <b><code>Ticker</code></b>-Objekte - Interfaces mit einer * Methode, die in immergleichen Abständen immer wieder aufgerufen werden.</p> * <p>Bewusst leitet sich diese Klasse nicht von <code>Thread</code> ab. Hierdurch kann ein Manager * ohne großen Ressourcenaufwand erstellt werden, wobei der Thread (und damit Computerrechenzeit) * erst mit dem aktiven Nutzen erstellt wird.</p> * * @author Michael Andonie * @author Niklas Keller <me@kelunik.com> * @see Ticker */ public class Manager { /** * <p>Der Standard-Manager.</p> * <p>Dieser wird nur innerhalb des "ea"-Paketes-verwendet!</p> * <p>Er ist der Manager, der verschiedene Ticker-Bedürfnisse von einzelnen internen Klassen * deckt und seine Fassung ist exakt an der Anzahl der nötigen Ticker angeglichen.</p> * <p>Dieser ist für: * <ul> * <li>Die Fensterkontrollroutine</li> * <li>Die Kollisionskontrollroutine der Klasse <code>Physik</code></li> * <li>Die Figurenanimationsroutine</li> * <li>Die Leuchtanimationsroutine</li> * </ul> * </p> */ @SuppressWarnings ( "StaticVariableOfConcreteClass" ) public static final Manager standard = new Manager(); /** * Alle möglichen Fontnamen des Systems, auf dem man sich gerade befindet.<br /> Hiernach werden * Überprüfungen gemacht, ob die gewünschte Schriftart auch auf dem hiesigen System vorhanden * ist. */ public static final String[] fontNamen; static { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); fontNamen = ge.getAvailableFontFamilyNames(); } /** * Prüft, ob ein Font auf diesem Computer existiert. * * @param name Der Name des zu ueberpruefenden Fonts * * @return <code>true</code>, falls der Font auf dem System existiert, sonst <code>false</code> */ public static boolean fontExistiert(String name) { for (String s : fontNamen) { if (s.equals(name)) { return true; } } return false; } private final ScheduledExecutorService executor; private final Map<Ticker, ScheduledFuture<?>> jobs; private int activeJobs = 0; /** * Vereinfachter Konstruktor ohne Parameter.<br /> Bei einem normalen Spiel muss nicht extra ein * Manager erstellt werden. Dafür gibt es bereits eine Referenz<br /> <br /> <code>public final * Manager manager;</code><br /> <br /> in der Klasse <code>Game</code> und damit auch in jeder * spielsteurnden Klasse. */ public Manager() { this.executor = Executors.newScheduledThreadPool(10); this.jobs = new HashMap<>(); } /** * Konstruktor eines Managers.<br /> Bei einem normalen Spiel muss nicht extra ein Manager * erstellt werden. Dafuer gibt es bereits eine Referenz<br /> <br /> <code>public final Manager * manager;</code><br /> <br /> in der Klass <code>Game</code> und damit auch in jeder * spielsteurnden Klasse. * * @param name Der Name, den der Thread haben wird, über den dieser Manager läuft.<br /> * Inzwischen ignoriert. * * @see #Manager() * @deprecated Nutze {@link Manager#Manager()} stattdessen, der Name wird inzwischen ignoriert. */ @Deprecated public Manager(String name) { this(); } /** * Diese Methode prüft, ob zur Zeit <b>mindestens 1 Ticker</b> an diesem Manager ausgeführt * wird. * * @return <code>true</code>, wenn mindestens 1 Ticker an diesem Manager zur Zeit mit seiner * <code>tick()</code>-Methode ausgeführt wird. Sonst <code>false</code>. */ public synchronized boolean hatAktiveTicker() { return this.activeJobs > 0; } /** * Meldet einen Ticker am Manager an. Ab sofort läuft er auf diesem Manager und damit wird auch * dessen <code>tick()</code>-Methode immer wieder aufgerufen. * * @param t Der anzumeldende Ticker * * @see #anmelden(Ticker) */ public synchronized void anmelden(Ticker t, int intervall) { anmelden(t); starten(t, intervall); } /** * Macht diesem Manager einen Ticker bekannt, <b>OHNE</b> ihn aufzurufen. * * @param t Der anzumeldende Ticker * * @see #anmelden(Ticker, int) */ public synchronized void anmelden(Ticker t) { if (istAngemeldet(t)) { Logger.warning("Der Ticker ist bereits an diesem Manager angemeldet und wird nicht erneut angemeldet."); return; } jobs.put(t, null); if (EngineAlpha.isDebug()) { Logger.info("Ticker wurde angemeldet: " + t.toString()); } } /** * Startet einen Ticker, der <b>bereits an diesem Manager angemeldet ist</b>.<br /> Läuft der * Ticker bereits, passiert gar nichts. War der Ticker nicht angemeldet, kommt eine * Fehlermeldung. * * @param ticker Der zu startende, <b>bereits am <code>Manager</code> angemeldete</b> * Ticker. * @param intervall Das Intervall im ms<sup>-1</sup>, in dem dieser Ticker ab sofort immer * wieder aufgerufen wird. * * @see #anhalten(Ticker) */ public synchronized void starten(final Ticker ticker, int intervall) { if (!istAngemeldet(ticker)) { Logger.error("Der Ticker ist noch nicht angemeldet."); return; } if (jobs.get(ticker) != null) { Logger.error("Ticker ist bereits am Laufen!"); return; } Runnable r = new Runnable() { @Override public void run() { // Otherwise these exceptions don'ticker show up anywhere! try { ticker.tick(); } catch (RuntimeException e) { Logger.error("Kritischer Fehler im Ticker: Eine Ausnahme wurde nicht abgefangen. Der Ticker wurde angehalten. Die folgende StackTrace sollte dir weitere Informationen liefern."); e.printStackTrace(); anhalten(ticker); } } }; this.jobs.put(ticker, executor.scheduleAtFixedRate(r, intervall, intervall, TimeUnit.MILLISECONDS)); this.activeJobs++; } /** * Prüft, ob ein Ticker t bereits angemeldet ist. * * @param t Der zu prüfende Ticker. * * @return <code>true</code>, falls der Ticker bereits an diesem <code>Manager</code> angemeldet * ist, sonst <code>false</code>. */ public synchronized boolean istAngemeldet(Ticker t) { return this.jobs.containsKey(t); } /** * Hält einen Ticker an, der <b>bereits an diesem Manager angemeldet ist</b>.<br /> Ist der * Ticker bereits angehalten, passiert gar nichts. War der Ticker nicht angemeldet, kommt eine * Fehlermeldung. * * @param ticker Der anzuhaltende Ticker * * @see #starten(Ticker, int) */ public synchronized void anhalten(Ticker ticker) { if (!istAngemeldet(ticker)) { Logger.error("Der Ticker ist noch nicht angemeldet."); return; } final ScheduledFuture<?> future = this.jobs.get(ticker); if (future != null) { this.jobs.get(ticker).cancel(false); this.jobs.put(ticker, null); this.activeJobs--; } if (EngineAlpha.isDebug()) { Logger.info("Ticker wurde angehalten: " + ticker.toString()); } } /** * Meldet einen Ticker ab.<br /> War dieser Ticker nicht angemeldet, so passiert nichts – * außer einer Fehlermeldung. * * @param ticker abzumeldender Ticker */ public synchronized void abmelden(Ticker ticker) { if (!istAngemeldet(ticker)) { Logger.error("Der Ticker ist noch nicht angemeldet."); return; } if (this.jobs.get(ticker) != null) { anhalten(ticker); } this.jobs.remove(ticker); if (EngineAlpha.isDebug()) { Logger.info("Ticker wurde abgemeldet: " + ticker.toString()); } } /** * Beendet den Thread, den dieser Manager verwendet und damit den Manager selbst. Sollte * <b>nur</b> aufgerufen werden, wenn der Manager selbst gelöscht werden soll. */ public final synchronized void kill() { alleAbmelden(); } /** * Macht diesen Manager frei von allen aktiven Tickern, jedoch ohne ihn selbst zu beenden. Neue * Ticker können jederzeit wieder angemeldet werden. */ public final synchronized void alleAbmelden() { for (Ticker ticker : jobs.keySet()) { if (jobs.get(ticker) != null) { anhalten(ticker); } } } }