2 Visualisierung
und Simulation mit Java 3 Lernmaterialien zur technischen Informatik |
Carsten Kelling 1997 (); letzte Änderung: 17. September 1997 |
Dieses Kapitel wendet sich an den Programmierer, der mit der Klassenbibliothek "Rechner-Baukasten" eigene Applets entwerfen möchte. Die Komponenten des eigentlichen Rechner-Baukastens haben zwei hervorstechende Fähigkeiten: Sie simulieren die typischen Bestandteile eines auf der Registertransferebene betrachteten Computers, und sie visualisieren die dabei auftretenden Zeitabläufe. Zusätzlich enthält der Rechner-Baukasten mehrere Klassen, die features bereitstellen, wie sie in den Demonstrations-Applets in den "Lernmaterialien zur technischen Informatik" (Kapitel 3) zu sehen sind.
Die Grundannahme beim Verfassen der nachfolgenden Abschnitte war, daß der Programmierer noch keine Erfahrungen mit dem Rechner-Baukasten gemacht hat. Deswegen wird bottom up vorgegangen.
Dieser Annahme gemäß stellt 4.1 Komponenten des Rechner-Baukastens gleich zu Beginn die 13 Klassen des eigentlichen Rechner-Baukastens in ihrer Hierarchie vor. Aus der hier vorhandenen Übersicht kann man schnell diejenige Klasse auswählen, die einer zu simulierenden Rechnerkomponente entspricht.
Abschnitt 4.2 Das Package ckelling.baukasten legt dar, warum der Rechner-Baukasten als ein sogenanntes Package abgefaßt wurde, und welche Vorteile dieses für Codeverwaltung und Programmsicherheit bietet.
In 4.3 Die Wahl der Basisklasse des Applets erfährt der Baukasten-Neuling, daß ihm durch den Rechner-Baukasten eine mächtige Basisklasse für Applets zur Verfügung gestellt wird, die er ableiten und so beliebig erweitern kann. Diese Klasse Rechner enthält wichtige Konstanten und Variablen, die für eine einheitliche Darstellung der Komponenten sorgen. Viele für die Simulation wichtige Variablen sind ebenfalls bereits vordefiniert. Dazu kommen einige Methoden, die sich um häufig benötigte Verfahren wie die Erweiterung von Zahlen auf beliebig viele Stellen oder die Anzeige von Fehlermeldungen, die der Benutzer wirklich wahrnimmt, kümmern. Zu den weiteren Leistungen von Rechner gehört, daß diese Klasse dafür sorgt, daß auf kleinen Bildschirmen mit dem verfügbaren Platz sparsam umgegangen wird. Schließlich zwingt sie den Programmierer dazu, bestimmte Methoden zu implementieren, die von Hilfsklassen oder für die Simulation benötigt werden.
4.4 Erzeugung der Komponenten geht ausführlich darauf ein, wie man Rechnerkomponenten in eigenen Applets erzeugt. Dazu weist 4.4.1 zunächst auf den grafischen Editor hin, mit dem sich Rechneraufbauten in WYSIWYG-Manier erstellen lassen. Er unterstützt die Fähigkeit aller Komponenten des Rechner-Baukastens, ihre optischen Repräsentationen nicht an festen Bildschirmkoordinaten erscheinen zu lassen, sondern aneinander "anzuschließen". Ein Register an einer Abzweigung eines Busses bleibt so grafisch immer mit dem Bus verbunden, auch wenn die Abzweigung Länge und Position ändert. Ebenso sind alle mit dem Editor erstellten Aufbauten dagegen resistent, daß sich die Bildschirmrepräsentation einer Klasse hinsichtlich der Größe ändert, solange diese Klasse ihre neue Größe den an ihr "angeschlossenen" Komponenten korrekt mitteilt.
Im weiteren Verlauf von 4.4 wird aber auch illustriert, wie man Aufbauten von Hand programmieren kann. Darum führt 4.4.2 Koordinaten der Komponenten die notwendigen Methoden auf, um die Position von Komponenten zu ermitteln und Komponenten so zu verschieben, daß sie "angeschlossen" erscheinen. Im Rahmen des Eingehens auf die Erzeugung von Komponenten werden in 4.4.3 die nicht allgemeinen Fähigkeiten einiger Klassen besprochen, 4.4.4 expliziert die sinnvolle Aufteilung der Komponenten in zwei Gruppen, und 4.4.5 beschäftigt sich mit dem Entfernen von Komponenten inklusive ihres Abbildes.
Nachdem man gesehen hat, wie man Rechnerkomponenten erzeugen und plazieren kann, gehen die Abschnitte 4.5 und 4.6 auf die beiden oben genannten Fähigkeiten der Klassen des Rechner-Baukastens ein, also auf die Simulation respektive Visualisierung.
In 4.5 Wertzuweisung und Berechnung wird umfassend beschrieben, wie man Komponenten einen Wert zuweist, und wie man diesen wieder abfragt. Es wird erläutert, wie ein RAM oder ein Befehlsregister mit Befehls- und Datenanteil gegebenenfalls vorher adressiert werden müssen bzw. können. Schließlich kann man nachlesen, wie die Klassen ALU und Adder Werte auch verarbeiten können.
In 4.6 Visualisierung wird beschrieben, wie die Simulationsvorgänge in der Zeit didaktisch adäquat visualisiert werden können.
In 4.7 Zusätzliche Funktionalität kann man nachlesen, wie man Komponenten des Rechner-Baukastens mit teilweise mehrzeiligen Überschriften versehen kann und wie man mit dem Rechner-Baukasten auf einfache Weise kleine kontextbezogene Hilfetexte am Mauszeiger - info tips - erscheinen läßt.
Erst nachdem man mit dem Rechner-Baukasten sein Handwerkszeug kennt, kann man sich mit der Frage beschäftigen, wie man mit diesen konkreten Mitteln Mikrorechner oder Teile von diesen simuliert. Deswegen folgt erst in 4.8 Der Simulationsansatz die Beschreibung zweier Simulationsansätze, die für die Demonstrations-Applets entworfen und dort eingesetzt wurden. Der zweite, aus dem ersten entstandene Algorithmus eignet sich auch dazu, Simulationsschritte rückgängig zu machen und ist durch unvorhergesehene Benutzereingaben nicht zu erschüttern.
Der Abschnitt 4.9 Pfade und benötigte Dateien geht auf die notwendige logische Verzeichnistruktur der "Lernmaterialien" ein, weil der Rechner-Baukasten hauptsächlich ihrer Komplettierung dienen soll. Die Ausführungen über die Verzeichnisordnung bei Verwenden von Packages sind aber auch für eigenständige Applets von Belang; da Javaprogramme keine monolithischen Dateien sind, ist es stets notwendig, die Auffindbarkeit aller benötigten Klassendateien sicherzustellen.
4.10 Ausführen eines Applets erklärt dazu, welche Anweisungen in einer HTML-Datei notwendig sind, um ein Applet zu starten und damit dieses seinen Klassencode findet. Außerdem wird das Übergeben von Parametern an ein Applet beschrieben; diese Parametrisierung wurde bei den Applets in 3.3 Der Von-Neumann-Rechner verwendet, um mit einer Klasse auf bisher fünf verschiedene Themen abheben zu können. Es werden die 13 vom Rechner-Baukasten unterstützten Parameter mit ihrer Bedeutung und erlaubten Werten aufgeführt. Mit ihnen läßt sich beispielsweise festlegen, bei welchen Komponenten eines Aufbaus Werte durch den Benutzer eingetragen werden können, ob Zahlen im Hauptspeicher als Opcodes erscheinen oder ob das Applet bereits über die erweiterten Knöpfe "", "" und "" verfügen soll. Mit diesen kann der Benutzer statt kleiner Demonstrationsschritte ganze Maschinenbefehle auf einmal ausführen lassen oder den Rechner sogar bis zu einem breakpoint laufen lassen.
4.11 enthält eine kurze Aufstellung der mit dem Rechner-Baukasten erstellten sieben Demonstrations-Applets, von "Register" über "Adressierungsarten" bis zu "Pipeline" und "Cache", und der von ihnen verwendeten Klassen.
4.12 Unterstützungsklassen zur freien Verwendung behandelt neun Klassen, mit deren Hilfe Applets leistungsfähiger, einfacher und ansehnlicher werden können. Der Verwendungszweck dieser Klassen, die die Entwicklung eigener Demonstrations-Applets deutlich erleichtern können, wird jeweils kurz genannt. Anschließend folgen erläuterte und kommentierte Codeausrisse, mit denen die Erzeugung und Benutzung veranschaulicht wird. Dieses Kapitel kann dem fortgeschrittenen Programmierer auch als Nachschlagewerk dienen.
Um diese Aneinanderreihung von Fakten, Anleitungen und Hinweisen nicht zu trocken werden zu lassen und damit
diese sich besser einprägen, wird im Laufe des Kapitels ein konkretes Beispiel verfolgt und Java-Code dazu
angegeben. Die entsprechenden Stellen sind mit dem Text "Beispiel: RAM"
markiert. Als Beispiel dient der aus 3.2.1.2 random access memory (RAM)
bekannte Aufbau, anhand dessen dort erläutert wurde, wie ein RAM arbeitet, insbesondere wie man es adressiert
und anschließend einen Wert hineinschreibt oder ausliest (Abbildung 31).
Für die Programmcodebeispiele geht der Autor von grundlegenden Kenntnissen des Lesers in objektorientierter
Programmierung aus - dieses umfaßt die Begriffe "Klasse", "Methode" und "Superklasse",
die Prinzipien der Vererbung (mit dem Verbum "ableiten") und Instanziierung, sowie die allgemeineren
Begriffe "Definition", "Deklaration" und "Initialisierung". Eine gute Einführung
in das objektorientierte Paradigma bietet [JavaTutorial].
Klassenhierarchie der Komponenten des Rechner-Baukastens
Abbildung 32 zeigt die Klassenhierarchie der Komponenten des Rechner-Baukastens. Mit den in der Einleitung erwähnten zusätzlichen Klassen beschäftigt sich eingehend 4.12 Unterstützungsklassen zur freien Verwendung; dort findet sich auch eine Grafik zu deren Klassenhierarchie.
Eine kurze Beschreibung aller Klassen, die zur Simulation und Darstellung von Teilen eines Computers auf der
Registertransferebene entworfen wurden, bietet Tabelle 9.
Klasse |
Kurzbeschreibung |
Adder | Ein Addierer mit beliebig vielen Eingängen. Abgeleitet von ALU; kann im Gegensatz zu dieser nur Addieren und sieht anders aus, erzeugt aber auch flags. |
ALU | Führt diverse Arten von arithmetischen und logischen Berechnungen mit einem oder zwei Operanden durch und
zeichnet das Symbol einer ALU inklusive Operanden, Operator, Resultat und flags. Das Bild der ALU ist skalier- und in Schritten von 90 Grad rotierbar; Operanden, Operator, Resultat und flags werden aber nur bei ""-ähnlicher ALU angezeigt. |
EditableLabel | Zeigt einen ganzzahligen numerischen Wert ohne Rahmen an; ist editierbar. |
EditableMemory | Simuliert ein RAM und zeichnet dieses. Speicherzellen sind editierbar, falls nicht gesperrt. |
Misc | Ellipse mit beliebig vielen Zeilen Text als Symbol für "sonstige" Komponenten |
Mux | 2-auf-1-Multiplexer. Die Beschriftung der beiden Eingänge (Standard: "1"/"0") läßt sich ändern. |
PipelineRegister | Register für Pipelines; die linke und rechte Hälfte können getrennt eingefärbt werden, um Schreiben oder Lesen anzuzeigen. Beliebig viele mit Namen versehene Werte können gespeichert werden; diese werden erst auf Kommando in der nächsten Pipelinestufe sichtbar. |
PipelineStageLabel | Überschrift für Stufen einer Pipeline. Zeigt zwei Zeilen Text an: Opcode des in der Stufe enthaltenen Befehls und Name der Stufe. |
RechnerKomponente | Superklasse für folgende Elemente des Rechner-Baukastens: ALU, Adder, EditableMemory,
TagMemory, SimpleBus, PipelineStageLabel, Misc, Mux. Kann abgeleitet werden, um eigene Komponenten für den Rechner-Baukasten zu erzeugen. |
RechnerKomponentePanel | Superklasse für folgende Elemente des Rechner-Baukastens: Register16, Register8, Register16Split,
PipelineRegister. Leitet sich von java.awt.Panel ab und kann abgeleitet werden, um eigene Komponenten für den Rechner-Baukasten zu erzeugen, die auch AWT-Komponenten (wie TextField) benutzen. |
Register16 | 16 Bit-Register (ohne Zwischenlinie); editierbar, falls nicht gesperrt. |
Register16Split | 16 Bit-Register (mit Zwischenlinie); Bytes einzeln editierbar, falls nicht gesperrt. |
Register8 | 8 Bit-Register (ohne Zwischenlinie); editierbar, falls nicht gesperrt. Achtung, speichert nur positive Werte! |
SimpleBus | Einfacher Bus; kann um beliebig viele Abzweigungen erweitert werden; ermittelt selbsttätig seinen Wert; kann Punkte von einem Startpunkt zu beliebig vielen Endpunkten laufen lassen; kann einen einzelnen Punkt über den Hauptstrang laufen lassen; Abzweigungen können dort, wo sie von dem Bus abgehen "Lötpunkte" oder Symbole für Tri-State-Gatter besitzen; der Hauptstrang des Busses kann farblich von den Abzweigungen abgehoben sein. |
TagMemory |
Simuliert ein RAM und zeichnet dieses. Speicherzellen sind editierbar, falls nicht gesperrt. Bietet Einstellmöglichkeiten für
|
Beispiel: RAM
Für den oben genannten RAM-Aufbau benötigt man offenbar ein RAM, zwei Register (Quelle für Adresse, Quelle/Senke für Daten) und zwei Busse (Adreß- und Datenbus). Mit Hilfe von Tabelle 9 entscheidet man sich leicht, folgende Variablen im Java-Code zu verwenden:
Mit Javas package-Anweisung können "Pakete" (Packages) für zusammengehörige Klassen geschnürt werden; eine Klasse wird als zu einem Package gehörig angesehen, wenn ihre erste Anweisung etwa
package ckelling.baukasten;
lautet. Das sorgt nicht nur für mehr Ordnung in den üblicherweise großen Mengen von Klassen, die für verschiedene Projekte entstehen, sondern erlaubt auch differenziertere Steuerung von Zugriffsrechten: Methoden oder globale Variablen und Konstanten, die als protected deklariert sind, dürfen (nur) innerhalb des gesamten Package und von allen abgeleiteten Klassen benutzt werden. Das ist ein sinnvoller Mittelweg zwischen der Deklaration als public, die Zugriffe durch alle existierenden fremden Klassen erlaubt, und der als private, die gegenüber allen anderen Klassen abschottet.
Alle Klassen, die zu dem Rechner-Baukasten gehören, seien es Rechnerkomponenten oder nützliche Zusatzklassen, gehören zu dem Package ckelling.baukasten. Das gilt ebenso für die Demonstrations-Applets.
Wenn eine Klasse zu einem bestimmten Package gehört, reicht dort, um eine Klasse desselben Package anzusprechen, der Name der zweiten Klasse. Deswegen heißt es in den Demonstrations-Applets beispielsweise
register = new Register16(...); // Eine Instanz von // ckelling.baukasten.Register16 erzeugen
Von außerhalb eines Package spricht man die Klassen innerhalb durch Davorstellen des Package-Namens an:
register = new ckelling.baukasten.Register16(...);
Wer auf Packages verzichtet, ordnet seine Klassen damit dem namenlosen Standard-Package zu, müßte also die aufwendigere Art der Klassenbezeichnung benutzen. Durch Zeilen der Art
import ckelling.baukasten.*; import java.awt.Frame;
zu Beginn des Codes (außerhalb von Klassendefinitionen) lassen sich aber ganze Packages oder nur bestimmte ihrer Klassen "importieren", und die jeweiligen Klassen brauchen fürderhin lediglich durch ihren Namen angesprochen zu werden.
Noch ein Wort zu der Namenswahl für das Package des Rechner-Baukastens: Gemäß offiziellem Diktum von Sun [JavaWhitepaper] sind als Packagenamen nur noch solche zugelassen, die mit einer umgedrehten Internet-Adresse beginnen; auf diese Art kann die weltweite Einheitlichkeit der Packagebezeichner garantiert werden. Der folglich korrekte Name de.uni-hamburg.informatik.ckelling.baukasten ist aber für dauerhaften Einsatz einfach zu lang.
Alle Aufbauten aus Komponenten des Rechner-Baukastens sollten sich statt von java.applet.Applet von ckelling.baukasten.Rechner ableiten (welche Klasse wiederum von Applet abgeleitet ist). Zumindest erwarten alle Komponenten bis auf einige der in 4.12 Unterstützungsklassen zur freien Verwendung beschriebenen Klassen einen Verweis auf eine Instanz von Rechner als "gemeinsame Basis".
Die einheitliche Basisklasse Rechner bietet einige Vorteile:
Beispiel: RAM
Das Grundgerüst für den Java-Code der Hauptklasse des RAM-Applets könnte daher so aussehen:
package ckelling.baukasten; 1. import java.applet.*; 2. import java.awt.*; import java.lang.*; import java.util.*; /** * Komponenten_RAM.java * * Demonstration der Rechner-Komponente "RAM" 3. * * @author Carsten Kelling * @version 0.8.1, 07.02.97 */ public class Komponenten_RAM extends Rechner 4. { public final static String VERSIONSTRING = "Komponenten: RAM 0.8.1"; 5. private EditableMemory ram; private SimpleBus abus; private SimpleBus dbus; private Register8 address; private Register16 register; public String getVersionString() 5. { return VERSIONSTRING; } public String getAppletInfo() 5. { return "Demonstration der Rechner-Komponente 'RAM'"; } public void init() // wird nach dem Start des Applets einmal aufgerufen { System.out.println(VERSIONSTRING); System.out.println("Initialisierung - Anfang"); // zur Fehlersuche initialize(new Dimension(510, 400), new Dimension(510, 380)); 6. ... (weitere Initialisierungen, z.B. der Rechnerkomponenten) System.out.println("Initialisierung - Ende"); // zur Fehlersuche }
Zu den Anmerkungen:
Für eine Aufzählung der standardmäßig von initialize() unterstützten Parameter, deren Wirkung und die Definition eines passenden Applet-Tags siehe 4.10 Ausführen eines Applets.
Eine Aufstellung aller Variablen und Konstanten sowie der Methoden mit benötigten Parametern findet sich in dem automatisch generierten, mit Kommentaren versehenen Dokument ckelling.baukasten.Rechner.html im Ordner Autodoku der "Lernmaterialien".
Trotz des im vorigen Abschnitt aufgeführten Code-Beispiels ist es zu empfehlen, das notwendige Grundgerüst des Applets mit dem Baukasten-Editor (siehe 4.4.1 Der Baukasten-Editor) zu erzeugen. Dennoch wird die Erzeugung von Instanzen der Klassen des Rechner-Baukastens im folgenden detailliert erläutert.
Der vom Baukasten-Editor erzeugte Code enthält nicht nur alle notwendigen Methoden-Rümpfe, um das Applet sofort starten zu können, sondern natürlich auch die Deklarationen und Initialisierungen der benötigten Rechnerkomponenten.
Abbildung 33 zeigt eine typische Arbeitssituation mit dem Baukasten-Editor; einige der Parameter, die mit ihm an Komponenten eingestellt werden können, sind zu erkennen. Dieses nützliche Werkzeug benötigt kaum eine Beschreibung zu seiner Funktion - starten Sie das Applet über die Datei Editor.html aus dem Verzeichnis der "Lernmaterialien" und fangen Sie an, Komponenten zu erzeugen und abzuwandeln.
So viel sei aber noch gesagt: Nachdem man sich nach einem Klick auf "Neu" für einen Komponententyp entschieden hat, wird sofort eine Instanz dieses Typs erzeugt und kann modifiziert werden. Da diese Instanz sich genau so verhält, wie alle Instanzen der jeweiligen Klasse, arbeitet der Editor folglich mit echtem WYSIWYG (what you see is what you get). Änderungen der Größe der Komponente oder ihrer Koordinaten werden bereits nach Änderung eines Wertes oder spätestens einem Klick auf "Übernehmen" sichtbar, bei anderen Änderungen muß erst das Eigenschaftenfenster per "OK" geschlossen werden.
Der Editor arbeitet zwar mit WYSIWYG, das heißt aber nicht, daß sich Komponenten mit der Maus verschieben oder vergrößern lassen; Änderungen können nur im jeweiligen Eigenschaftenfenster vorgenommen werden.
Um die Eigenschaften einer Komponente nachträglich zu ändern, muß der entsprechende Eintrag in der Baumansicht mit der Maus markiert werden, bevor auf "Ändern" geklickt wird; auch Abzweigungen von Bussen können so nachträglich verändert werden. Analog funktioniert das Löschen von Komponenten.
Busse können erst mit Abzweigungen versehen werden, wenn ihr Eigenschaftenfenster ein erstes Mal per "OK" geschlossen worden ist.
Der erzeugte Java-Code wird nach einem Klick auf "Code erzeugen" in der Java-Konsole angezeigt. Leider scheiden deswegen MS Internet Explorer und appletviewer als virtuelle Maschine für den Editor aus, denn ersterer zeigt keine Konsole an und letzterer benutzt ein MS-DOS-Fenster, das nur wenige Zeilen faßt. Obendrein zeigen beide sich durch die große Anzahl von AWT-Komponenten des Editor-Applets überfordert, was zu Darstellungsfehlern führt. In Netscape wird die Java-Konsole über "Show Java Console" aus dem "Options"-Menü angezeigt; es empfiehlt sich, ihren Inhalt direkt vor dem Klick auf "Code erzeugen" zu löschen. Leider können Aufbauten aus dem Editor (noch) nicht direkt abgespeichert werden, da die Dateizugriffsrechte für Applets (noch) uneinheitlich geregelt sind.
Wenn man sich den von dem Editor erzeugten Initialisierungscode für die Komponenten ansieht, fällt auf, daß X- und Y-Koordinate, sowie Breite und Höhe aller Komponenten absolute Werte sind. Wer sich das Applet zur Speicherhierarchie angesehen hat, bei dem sich der Tag-Speicher automatisch verschiebt, wenn der Datenspeicher des Cache seine Breite ändert, hätte vielleicht erwartet, daß Komponenten des Baukastens statt reiner int-Werte Verweise auf andere Komponenten übergeben bekommen. In einem solchen Modell würde die Änderung von Größe oder Position einer Komponente zur Laufzeit Änderungen bei allen davon abhängigen Komponenten bewirken. Der Autor hat sich aber aus mehreren Gründen gegen ein solches Vorgehen entschieden:
Wie der Editor zeigt, ist es nicht so, daß man die Koordinaten und Breiten-/Höhenangaben seiner Komponenten stets und vollständig per Hand ausrechnen muß. Der Editor bietet es an, diese vier Angaben (bzw. fünf bei Busabzweigungen) entweder als absoluten Zahlenwert einzugeben, oder aber eine Komponente zu benennen, von der sich beispielsweise die X-Koordinate ableitet; zusätzlich muß man noch angeben, welche der X-Koordinaten, die sich an der anderen Komponente finden lassen, man benutzen möchte, beispielsweise die des linken oder rechten Randes. Außerdem läßt sich - kein großes Kunststück - optional ein fester Aufschlag angeben. Im RAM-Beispiel sorgen folgende Zeilen dafür, daß der Datenbus immer exakt in der Mitte des unteren Randes des RAMs beginnt:
Beispiel: RAM
//// DATENBUS //// Point coord = ram.getCoordinates("bottom"); dbus = new SimpleBus("Datenbus", coord.x, coord.y, 0, 185, (Rechner) this);
Anmerkung: Ein java.awt.Point ist ein Objekt mit einer X- und einer Y-Koordinate, zwei int-Werten. In diesem Fall ist ein neuer Bus (Instanz von SimpleBus) erzeugt worden; hätte dbus bereits existiert, hätte der Code so lauten müssen:
Beispiel: RAM
Point coord = ram.getCoordinates("bottom"); dbus.setCoordinates(coord.x, coord.y, "Wird bei Bussen als 'leftTop' angenommen");
Der Zeichenkettenparameter bei getCoordinates() beschreibt offenbar den Punkt an der Komponente, dessen Koordinaten man erfahren möchte; um die Wirkung des entsprechenden Parameters bei setTT>Coordinates() zu erläutern, noch etwas mehr Code:
Beispiel: RAM
coord = dbus.getCoordinates("register", "end"); 1. register = new Register16("Register", coord.x, coord.y, "right", (Rechner) this); register.setEditable(true); 2. add(register); 3. register.show(); 3.
Zunächst sind zu diesem Codestück noch einige Anmerkungen nötig:
Die Abzweigung "register" des Busses geht von diesem nach links ab und liefert durch obigen Aufruf an getCoordinates() die Koordinaten des Punktes ganz links. Ohne den Parameter "right" in der Konstruktormethode müßte man, um etwa ein Register dort an den Bus anzuschließen, den Punkt coord erst soweit nach links verschieben, wie die neu zu erzeugende Komponente breit wäre. Diese Vorgehensweise ist bei Bussen notwendig, die den durch coord.x und coord.y beschriebenen Punkt stets als linke, obere Ecke ihrerselbst auffassen. Das neue Register oben jedoch benutzt (coord.x; coord.y) als den Punkt in der Mitte seiner rechten Seite, eben wegen des "right".
Hätte das Register bereits existiert, hätte der Aufruf
register.setCoordinates(coord.x, coord.y, "right");
es an dieselbe Stelle verschoben. Für die Plazierung von Komponenten existieren also die beiden in den folgenden Abschnitten aufgeführten Methoden.
Durch einen Aufruf der Methode getCoordinates(String qualifier) liefern Komponenten des Rechner-Baukastens die Koordinaten bestimmter "markanter" Punkte. Die Menge der unterstützten qualifier ist von der jeweiligen Klasse oder gar Instanz abhängig und läßt sich durch einen Aufruf von getPossibleQualifiers() ermitteln; alle Komponenten kennen den qualifier "leftTop" für die Koordinaten der linken, oberen Ecke (des kleinsten umfassenden Rechtecks bei Komponenten mit abgerundeten Ecken etc.). Tabelle 10 führt alle möglichen qualifier auf; man beachte, daß Busse eine Variante von getCoordinates() mit zwei Parametern kennen (in Klammern).
Sollte ein Parameter nicht unterstützt werden, wird er als "leftTop" angenommen und eventuell (bei Bussen) eine Fehlermeldung ausgegeben.
Durch einen Aufruf von setCoordinates(int x, int y, String grabMode) läßt sich jede Komponente des Rechner-Baukastens mit der durch grabMode bezeichneten "markanten" Stelle an den Punkt (x; y) verschieben. Für grabMode sind grundsätzlich alle Werte erlaubt, die auch qualifier bei getCoordinates()annehmen kann. Die Klassen EditableMemory, SimpleBus und TagMemory ignorieren diesen Parameter jedoch vollständig, d.h. sie nehmen grabMode als "leftTop" an.
Neben den Methoden, die von allen Komponenten des Rechner-Baukastens unterstützt werden und von denen im weiteren Verlauf des Kapitels die Rede sein wird, kennen die meisten Komponenten natürlich zusätzliche Methoden, um der Rolle der durch sie simulierten Rechnerkomponente gerecht zu werden.
So läßt sich das Bild einer ALU in Schritten von 90 Grad drehen oder ein RAM anweisen, ob es per Mausklick editierbar sein soll und ob es Opcodes statt Zahlenwerte anzeigen soll.
Einige dieser "Spezialitäten" lassen sich bereits über das Editor-Applet einstellen; in
der nachstehenden Tabelle sind die Methoden aufgeführt, die dazu benötigt werden. Diese Aufstellung diene
als kurze Übersicht der am häufigsten benötigten Modifikationsmöglichkeiten. Einen vollständigen
Überblick liefert die Klassendokumentation in HTML-Form - Einstiegspunkt dazu ist die Datei packages.html
aus dem Verzeichnis Autodoku im Verzeichnis der "Lernmaterialien".
"Qualifier" |
unterstützt von Klasse(n) |
liefert Koordinaten von welchem Punkt |
"leftTop" | (allen) | linke, obere Ecke des kleinsten umfassenden Rechtecks |
"left" "right" |
ALU EditableLabel EditableMemory Misc PipelineRegister PipelineStageLabel Register16 Register16Split Register8 horizontaler SimpleBus TagMemory |
Mitte der linken/rechten Seite Bei einem horizontalen SimpleBus Synonym für "start"/"end" (s.u.). Bei ALU Abkürzung für "leftInput"/"rightInput" (s. u.). |
"top" (oder "clock" bei Registern) "bottom" |
Adder EditableMemory Misc Mux PipelineRegister PipelineStageLabel Register16 Register16Split Register8 vertikaler SimpleBus TagMemory |
Mitte der oberen/unteren Seite Bei einem vertikalen SimpleBus Synonym für "start"/"end" (s.u.). |
"rightTop" "leftBottom" "rightBottom" |
EditableMemory TagMemory |
entsprechende Ecke des kleinsten umfassenden Rechtecks |
"start" "end" |
SimpleBus | Anfangspunkt des Busses (links/oben) Endpunkt des Busses (rechts/unten) |
z.B. ("ireg", "start") z.B. ("ireg", "end") |
SimpleBus | Punkt, an dem die Abzweigung "ireg" am Bus beginnt/an dem sie endet. |
z.B. "ireg" | SimpleBus | Abkürzung für ("ireg", "end") |
"upper" / "upperInput" "lower" / "lowerInput" "output" |
Adder ALU ("" oder "") Mux |
oberer Eingang unterer Eingang Ausgang |
"left" / "leftInput" "right" / "rightInput" |
ALU ("" oder "") | linker Eingang rechter Eingang |
Spezifische Methode |
Gegenstück |
unterstützt von Klasse(n) |
Wirkung |
setEditable() | isEditable() | EditableLabel EditableMemory Register16 Register8 Register16Split TagMemory |
Zellen können angeklickt und editiert werden; erlaubt ggf. auch das Setzen von breakpoints und in Tag-Speichern das Einstellen von "valid" oder "dirty". |
setOrientation() | getOrientation() | ALU | Ausrichtung der ALU: "", "", "" oder "" |
setMemorySize() setBitWidth() setLineSize() initRam() allowBreakpoints() |
getMemorySize() getBitWidth() getLineSize() getProgram() breakpointsAllowed() |
EditableMemory TagMemory |
Anzahl der Speicherzellen, Bits pro Speicherzelle, linesize, geladenes Programm einstellen; Setzen von breakpoints zulassen. |
setOpcodesSupported() showOpcodes() |
getOpcodesSupported() opcodesAreShowing() |
EditableMemory | Mitteilen, ob dieses RAM Opcodes anzeigen können soll; Opcodes statt Zahlen anzeigen (falls erlaubt). |
setCacheSize() setAssociativity() setWriteMode() setReplaceMode() |
getCacheSize() getAssociativity() getWriteMode() getReplaceMode() |
TagMemory | Größe, Assoziativität, Schreibverhalten und Ersetzungsstrategie eines Cache festlegen. |
setFlags(int f) setFlags(String edgeName, int f) und weitere |
getFlags() getFlags(String edgeName) und weitere |
SimpleBus | Farbe des Hauptstranges (wie Abzweigungen in EDGE_COLOR oder aber in BUS_COLOR) wählen; sollen Abzweigungen "Lötpunkte" oder Symbole für Tri-State-Gatter besitzen? |
Alle Komponenten des Rechner-Baukastens leiten sich von einer von zwei Klassen ab: RechnerKomponente oder RechnerKomponentePanel; deren gemeinsame Superklasse ist Object, also der kleinstmögliche gemeinsame Nenner zweier Klassen in Java. Wegen dieser unterschiedlichen "Herkunft" der Komponenten ist es ohne besondere Vorkehrungen nicht möglich, eine Methode zu schreiben, die beliebige Rechnerkomponenten, also z.B. eine Instanz von SimpleBus oder Register16, als Parameter übergeben bekommt. Abbildung 34 zeigt noch einmal die Klassenhierarchie des Rechner-Baukastens, um diese Aufteilung zu verdeutlichen.
Diese Unterscheidung der Rechnerkomponenten in Abkömmlinge von RechnerKomponente und solche von RechnerKomponentePanel ist natürlich nicht ohne Grund erfolgt. Historisch entstand zuerst RechnerKomponente unter folgenden Zielsetzungen:
Schließlich aber wurde es opportun, auch für Rechnerkomponenten (statt nur für die Steuerfenster) einige AWT-Elemente zu benutzen, um das Rad, sprich einen zuverlässig funktionierenden Eingabebereich für Text oder einen Scrollbalken, nicht neu erfinden zu müssen. Auch lassen sich bei diesen schon existenten Komponenten viel leichter die Benutzereingaben, in Form von Mausklicks und Tastendrücken, abfragen (Stichwort events) und steuern (um z.B. die Eingabe unzulässiger Zeichen zu unterbinden). Damit die events aber auch in der Rechnerkomponentenklasse "landen", die etwas mit ihnen anzufangen weiß, muß diese java.awt.Panel ableiten - die Alternative wäre es gewesen, alle benötigten AWT-Komponenten an das Rechner-Objekt zu exportieren, wobei der Programmierer für jede neue Komponente aber dort die Methode handleEvent() hätte erweitern müssen.
Selbstverständlich gibt es auch hier eine Ausnahme: EditableMemory (und das von ihm abgeleitete TagMemory) sind "nur" Erben von RechnerKomponente, obwohl sie AWT-Komponenten benutzen. Das wurde möglich, weil EditableMemory seine aufwendige Grafikdarstellung von einer eigenen, von Panel abgeleiteten, Klasse EditableMemory_Ressource erledigen läßt. Bei einer Instanziierung von EditableMemory wird automatisch eine Instanz von EditableMemory_Ressource erzeugt und dem Rechner-Objekt hinzugefügt. Diese Bequemlichkeit erkauft man sich allerdings damit, daß man bei einem Entfernen des Speichers extra dessen remove() aufrufen muß (siehe 4.4.5 Entfernen von Komponenten), um auch das Bild verschwinden zu lassen. Für die wenigen von Register16 benutzten AWT-Komponenten hielt der Autor eine eigene, für das Bild zuständige Klasse jedoch für übertrieben aufwendig.
Beispiel: RAM
Die paint()-Methode dieses Applets, die dafür sorgen soll, daß alle verwendeten Komponenten neu gezeichnet werden, könnte vereinfacht lauten:
public synchronized void paint (Graphics onScreenGC) { // Fläche des Applets (unsichtbar im Hintergrund) löschen offScreenGC.setColor(BACKGROUND); Rectangle size = bounds(); offScreenGC.fillRect(0, 0, size.width, size.height); ram. paint(offScreenGC); // Komponenten abus. paint(offScreenGC); // zeichnen sich dbus. paint(offScreenGC); // im unsichtbaren address. paint(offScreenGC); // Hintergrundbild register.paint(offScreenGC); // neu// Hintergrundbild auf den sichtbaren Bildschirm kopieren onScreenGC.drawImage(offScreenImage, 0, 0, this); }
Offenbar wird jede vorhandene Komponente explizit genannt und ihre paint()-Methode aufgerufen. Man beachte außerdem die Verwendung von double buffering (siehe hierzu und zu dem Neuzeichnen der Komponenten allgemein 4.6.4 Neuzeichnen der Komponenten).
Als Abhilfe entstand während der Arbeiten an dem Editor-Applet die Klasse RKWrapper. Sie ist ein sogenannter wrapper für RechnerKomponente und RechnerKomponentePanel, d.h. sie verbirgt in ihrem Inneren eine Instanz der einen oder der anderen Klasse, kennt alle Methoden, die RechnerKomponente und RechnerKomponentePanel gemeinsam sind, und ruft bei einem Aufruf einer Methode die korrespondierende Methode des Objektes in ihrem Inneren auf (und liefert gegebenenfalls den Wert zurück, den das "umfaßte" Objekt liefert). Mit RKWrapper ist es dann doch möglich, alle Komponenten eines Rechner-Applets in gleicher Form zu behandeln; eine allgemeine paint()-Methode, die auch für das RAM-Beispiel gälte, könnte dann so aussehen:
Beispiel: RAM
public synchronized void paint(onScreenGC) { // Fläche des Applets löschen offScreenGC.setColor(BACKGROUND); Rectangle size = bounds(); offScreenGC.fillRect(0, 0, size.width, size.height); // Von allen Komponenten wurde ein RKWrapper erzeugt // und im Vektor elements abgelegt. for (int i = 0; i < elements.size(); i++) ((RKWrapper) elements.elementAt(i)).paint(onScreenGC); // Hintergrundbild auf den sichtbaren Bildschirm kopieren onScreenGC.drawImage(offScreenImage, 0, 0, this); }
In den Demonstrations-Applets wurde aus Gründen der Les- und Wartbarkeit die obere Variante benutzt. Dieses erlaubt, in zur Laufzeit unveränderlichen Methoden wie paint() oder updateAll() jede Komponente direkt im Code zu nennen und anzusprechen.
Manchmal kann es notwendig sein, Komponenten zur Laufzeit zu entfernen. Das Speicherhierarchie-Applet (Funktionsweise eines Cache) beispielsweise löscht die beiden den Cache bildenden Speicher, sobald bestimmte Parameter verändert werden, und erzeugt zwei neue.
Zunächst einmal läßt sich jede Komponente grundsätzlich durch Setzen auf null löschen. Beachten Sie dabei jedoch:
2 Visualisierung
und Simulation mit Java 3 Lernmaterialien zur technischen Informatik |
Carsten Kelling 1997 (); letzte Änderung: 17. September 1997 |