Extended VT52-Emulator Teil 3

Bild 1: Arbeiten mit zwei Textfenstern bei Verwendung von xVT52

Einige der neu hinzugekommenen Steuerzeichen wurden bereits im zweiten Teil dieser Serie behandelt. In dieser Folge werden auch noch die restlichen ESCapes unter die Lupe genommen, bevor es dann um den Geschwindigkeitszuwachs gehen wird...

... und der ganze Rest

Beginnen wir also mit dem Komplettieren der xVT52-Sequenzen. In Programmen ist es durchaus nicht unüblich, daß es mehrere Prozeduren oder Funktionen gibt, die in irgendeiner Weise auf den (“TOS”-) Bildschirm zugreifen, um z.B. Texte auszugeben oder auch Eingaben anzufordern. Nun ist das mit dem Ein- und Ausschalten des Cursors so eine Sache. Angenommen, das Hauptprogramm möchte verhindern, daß der Cursor innerhalb einer Prozedur oder Funktion sichtbar werden wird. Dann läßt sich dies mit den konventionellen Möglichkeiten des VT52-Emulators nicht mehr erreichen. Ein Blick ins VDI-Handbuch zeigt aber anhand der Maus-Routinen, daß es auch anders geht. Unter der Nummer 122 läßt sich die Funktion SHOW CURSOR ansprechen, der man eine Variable mitgeben kann. Je nach Wert dieser Variablen wird die Maus entweder sofort sichtbar (0), oder es wird zuerst ein Zähler dekrementiert, der von der Funktion HIDE CURSOR (VDI 123) inkrementiert wird (1). Erst wenn dieser Zähler auf Null liegt, wird die Maus wieder angezeigt. Was liegt also näher als diesen Mechanismus auch für den Cursor einzuführen? Hierzu dient die Sequenz:

Der Cursor wird also erst wieder sichtbar, wenn genausoviele ESC a-wie ESC f-Aufrufe erfolgt sind. Der Zähler wird durch ESC e auf Null gesetzt, so daß nach diesem Aufruf der Cursor garantiert sichtbar ist, und bei jedem ESC f inkrementiert (wobei dann der Cursor garantiert unsichtbar wird).

Schwarz auf Weiß und umgekehrt

Um die Augen und/oder den Bildschirm zu schonen, kann es nützlich sein, die Darstellung zu invertieren, d.h. alles, was vorher weiß war, wird jetzt schwarz und umgekehrt. Für diesen Fall kennt der erweiterte Emulator folgende Steuerzeichen:

Intern bewirken diese beiden Funknonen lediglich eine Umprogrammierung des Video-Shifters; mit jeweils zwei Assembler-Befehlen kann man sie getrost als die “weniger Code-intensiven” Teile des Emulators bezeichnen. Bemerkenswert ist noch, daß wegen der Shifter-Programmierung natürlich ALLES invertiert wird, also auch das Desktop und alles, was mit GEM zu tun hat. Um also nicht gewollte Effekte zu verhindern, sollten Sie Ihre TOS-Programme noch ein ESC n ausgeben lassen, bevor sie terminieren (sofern die Darstellungsart zwischenzeitlich gewechselt wurde, versteht sich).

X=Print C$ oder: Als die Ausgabe zur Funktion wurde...

Zugegebenermaßen ist es reichlich ungewöhnlich, daß man bei der Zeichenausgabe über Funktionen des Betriebssystems einen Rückgabewert erhält. Aber einerseits liefern eigentlich alle GEMDOS-/BIOS- und XBIOS-Funktionen irgendwelche Werte (das haben Funktionen halt so an sich), andererseits wissen die Kenner der Sprache C, daß printf() auch etwas zurückgibt, nämlich die Anzahl der ausgegebenen Argumente. Für den erweiterten Emulator gab es zwei Probleme, die eine Werte-Rückgabe notwendig machten. Zum einen mußte es eine “legale” Möglichkeit für Programme geben, um feststellen zu können, ob der xVT52-Treiber installiert ist (um gegebenenfalls mit einer Meldung abbrechen zu können; oder andere Routinen für den Standard-VT52-Emulator anzustoßen). Zum anderen sollte der Benutzer ebenfalls eine “legale” Möglichkeit erhalten, um auf alle relevanten Variablen des Emulators zugreifen zu können. Beides läßt sich durch die Einführung zweier Sequenzen erreichen:

Sollten Sie nicht mehr wissen, wozu die beiden Strukturen dienen, hier nochmal die Kurzfassung. Der Terminal Control Block (TCB) enthält alle zum Organisieren der Ausgabe wichtigen Parameter, wie z.B. die aktuelle Cursorposition, den aktiven Font, Attribute (wrapping, invers...) u.ä., während der Cursor Control Block die für die Darstellung des Cursors benötigten Variablen enthält (wie Blinkrate, aktuelle Position und Cursorattribute). Wenn ein Programm nun feststellen möchte, ob der xVT52-Emulator bereits geladen wurde, kann es sich einer Sequenz bedienen, wie dies in Listing 2 exemplarisch anhand eines GFA-Progrämmchens dargestellt ist. Nur bei installiertem Treiber liefern die Ausgabefunktionen die gewünschten Zeiger. Die Differenz zwischen den Zeigern muß genau 66 betragen, ansonsten kann man sicher davon ausgehen, daß nur der Original-Emulator seinen Dienst verrichtet. Damit wären die erwähnten Probleme auch schon aus der Welt geschafft, denn gleichzeitig mit den Zeigern hat man ja auch den Offset auf die xVT52-Variablen und somit die Möglichkeit, auf diese zuzugreifen. Aber Vorsicht!

Fummel, fummel...

Sicher ist es richtig, daß man oft erst durch gezielte Eingriffe in programminterne Bereiche Effekte erreichen kann, die legal unmöglich zu realisieren gewesen wären. Genauso sicher ist es aber auch, daß der am häufigsten dabei erzielte Effekt ein Systemabsturz ist. Der erweiterte Emulator gestattet dem Programmierer einen sehr tiefen Einblick in seine Arbeit. Das Lesen der Variablen ist natürlich absolut ungefährlich; so ist sicherlich nichts dagegen einzuwenden, wenn man z.B. die aktuelle Schreibposition ermitteln möchte. Wohl aber, wenn Variablen geändert werden, ohne sich der Folgen bewußt zu sein! Das Ändern des einen oder anderen Bytes im TCB und CCB kann wunderschöne Effekte mit sich bringen, einschließlich des oben beschriebenen, versteht sich. Für die meisten denkbaren Änderungen stehen ohnehin ESCape-Sequenzen zur Verfügung; allerdings läßt sich ein selbstgeschriebener Zeichensatz (muß denselben Aufbau wie der des GEM und eine konstante Breite von 8 Pixeln haben) mittels direktem Einträgen in den TCB schneller und wohl auch einfacher installieren als über das VDI.Man sollte vor derartigen “Fummeleien” aber dafür sorgen, daß alle Daten, die man evtl, später nochmal benötigen könnte, einer sorgfältigen Rettungsaktion unterzogen werden; sonst - HUAAaaahh!

Um hier allerdings keine falschen Eindrücke aufkommen zu lassen: xVT52 läuft bereits seit Monaten unter den verschiedensten Programmen ohne Fehl und Tadel. Es ist also bei Aktivieren des erweiterten Emulators keineswegs “bloß noch eine Zeitfrage”, wann sich der Rechner wohl verabschieden würde. Insofern bezieht sich meine Mahnung nur auf die vom Anwender eigenmächtig vorgenommenen Manipulationen und deren eventuelle Folgen.

Text-” Windows”

Wohl jedes benutzerfreundliche Programm beinhaltet auf die eine oder andere Art die Möglichkeit, dem Benutzer Hilfestellungen zu geben, wenn er an einer Stelle nicht mehr weiter weis. Innerhalb von GEM-Programmen läßt sich dies mittels eines zusätzlichen Fensters erreichen (sofern nicht schon vier geöffnet sind; aber dann gibt es ja auch noch Alert-und Dialogboxen...). Unter TOS muß man da schon größere “Verrenkungen” anstellen, weil es halt mal nur einen Bildschirm gibt. Und alle VT52-Funktionen - speziell diejenigen zum Einfügen und Löschen einer Zeile - beziehen sich prinzipiell auf den kompletten Bildschirm, wodurch der Einsatz eines “geschützten” Bildschirmbereiches unmöglich gemacht wird. Zwei Sequenzen des xVT52-Emulators erlauben es nun, zwei voneinander unabhängige Textfenster zu unterhalten, indem sie die bereits bestehenden ESCapes L (zum Einfügen) und M (zum Löschen) quasi “im gemischten Doppel” nachvollziehen:

Tabelle 1: Alle Scrollfunktionen auf einen Blick

Nach diesen Sequenzen befindet sich der Cursor am Anfang (Spalte 0) der eingefügten bzw. gelöschten Zeile. Sie arbeiten somit genauso wie die bisherigen Steuer-Sequenzen, nur in der Behandlung des restlichen Bildschirminhaltes genau umgekehrt. Damit kann man nun problemlos zwei Bildschirmbereiche steuern. Weil es aber einigermaßen umständlich ist zu erklären, in welchem Bereich wie gearbeitet werden muß, möchte ich Sie auf Bild 1 hinweisen, in dem die benötigten ESCapes den beiden Fenstern zugeordnet sind. Es bleibt lediglich Ihrer Phantasie überlassen, was Sie mit dem zweiten Fenster alles anstellen: Sie können damit ebenso Hilfstexte einblenden wie auch in Editormanier zwei Texte bearbeiten. Sie müssen nur darauf achten, daß der Bildschirminhalt gepuffert wird, damit ein durch das zweite Fenster eventuell zerstörter Text wiederhergestellt werden kann. Ansonsten können mit den neuen Sequenzen auch hübsche Effekte erzielt werden: Wenn Sie - ausgehend von Zeile 12 -abwechselnd mit ESC s und ESC L Zeilen einfügen oder mit ESC t und ESC M Zeilen löschen, entsteht der Eindruck, der Bildschirm würde “explodieren” bzw. “implodieren”. Besonders lustig sieht das “Implodieren” bei Verwendung der pixelorientierten Sequenzen aus, die Sie gleich kennenlernen werden.

Seitensprünge (ESCapaden)

Daß man Texte nach oben und unten scrollen kann, ist sicherlich ein Standard-Feature eines jeden Emulators. In einer pluralistischen Gesellschaft wie der unseren sollte es aber fast schon eine Selbstverständlichkeit sein, in allen vier Himmelsrichtungen Freizügigkeit zu genießen... Also wurden beim xVT52 zwei weitere Sequenzen implementiert, die ein spaltenweises Scrollen ermöglichen:

In beiden Fällen bleibt der Cursor an derselben Position stehen, an der er auch stand, bevor gescrollt wurde; er ändert also weder Zeilen- noch Spaltenposition. Das “Durchlaufenlassen” bedeutet hier, daß das Zeichen, welches durch das Scrollen links oder rechts “herausgefallen” ist, auf der jeweils anderen Seite wieder “hereingeschoben” wird. Wozu läßt sich das gebrauchen? Sie können beispielsweise damit eine Eingaberoutine schreiben, die - sobald die letzte Spalte der aktuellen Zeile erreicht ist - den bisherigen Inhalt um eine Spalte nach links schiebt und die letzte Spalte zum Eingeben eines neuen Zeichens löscht. Wenn Sie sich den Editor von GFA-BASIC einmal näher betrachten, werden Sie feststellen, daß dort genau dies passiert. Nur: Das Feuerwerk an ESCape-Sequenzen in Kombination mit wiederholter Textausgabe läßt erahnen, welche Verrenkungen Sie sich mit ESC u/x sparen können!

Wem diese beiden Sequenzen nicht genügen, der dürfte - Assembler-Kenntnisse vorausgesetzt - keine Probleme haben, die entsprechenden Prozeduren SCR_LEFT bzw. SCR_RIGHT auf seine Bedürfnisse umzustricken, so daß es dann möglich ist, ab der aktuellen Spalte den Rest (ohne Turn-around) nach links oder rechts zu scrollen.

Bit By Bit - Built-In Smooth Scrolling

Hinter diesem netten Zungenbrecher verbirgt sich eine Besonderheit von xVT52, die bei vielen Effekten wertvolle Hilfe bieten kann. Gemeint ist das pixelweise Verschieben des Bildschirminhaltes. Zu allen Steuerzeichen, die der erweiterte Emulator kennt und die dem Einfügen/Löschen und horizontalen Scrollen dienen, gibt es ein “pixelweises Pendant“. In allen Fällen bleibt der Cursor auf seiner alten Position stehen. Soll - wie bei den normalen Sequenzen - der Cursor im Anschluß an die jeweilige Operation am Anfang der bearbeiteten Zeile stehen, so kann man dies durch nachträgliches Ausgeben eines Zeilenrücklaufes (Carriage Return, ASCII-Code 13) erreichen. Tabelle 1 zeigt den Zusammenhang zwischen Sequenzen zum zeilen-/spaltenweisen Scrollen und den entsprechenden, pixelweise arbeitenden Verschieberoutinen. Anzumerken ist hier noch, daß bei den spalten weisen Verschieberoutinen (ESC T/U) tatsächlich nur um ein Pixel nach links bzw. rechts gescrollt wird. Möchten Sie also eine Zeile um eine komplette Spalte verschieben, müssen Sie die entsprechende Sequenz achtmal abschicken. Warum? Weil der Prozessor abgeht wie die Feuerwehr! Würden diese Sequenzen jeweils eine komplette Spalte verschieben, wären sie überhaupt nicht mehr von den spaltenorientierten Routinen zu unterscheiden...

Alphabet (fast) komplett!

Wer mitgezählt hat, wird auf bisher 51 (!) ESCape-Sequenzen kommen, die der erweiterte VT52-Emulator “versteht”, nämlich von A-Z und a-z. Die letzte Sequenz, ESC r, dient dazu, den Emulator grafikfähig zu machen. Es ist sicherlich die reizvollste Erweiterung des VT52, mit der sich etliches anstellen läßt. Um den Rahmen nicht zu sprengen, muß ich Sie aber leider auf den vierten und letzten Teil dieser Serie vertrösten. Da Sie sowieso erst mit dem Emulator arbeiten können, wenn Sie ihn komplett eingegeben haben, dürfte dies aber leicht zu verschmerzen sein. Es ließ sich nicht vermeiden, den Artikel in vier Teile zu splitten, weil er “en bloc” schlicht-weg zu groß geworden wäre. Andererseits war es aber unmöglich, das Programm zu modularisieren, so daß Sie also portionsweise immer ein “Stückchen mehr vom Emulator” gehabt hätten. Denn: Entweder läuft alles oder es läuft eben nichts. So ist das nunmal im Digitalgeschäft...

Tabelle 2: Ausgabegeschwindigkeiten mit und ohne xVT52 bei verschiedenen Konfigurationen und TOS-Versionen. Alle Zeiten in Sekunden.

Speed It Up

Wenn man ein Programm schreibt, das ein bereits bestehendes Programm ablösen soll, muß man dafür natürlich gute Gründe haben. So auch bei der Vorstellung eines neuen (alten, weil kompatiblen) Terminal-Emulators. Nun kann der erweiterte Emulator mit einer Vielzahl wirklich neuer Features aufwarten, aber das hätte man sicher auch anders erreichen können; beispielsweise durch Einführung neuer GEMDOS-Funktionen. Die Einbindung in den Emulator ist natürlich die wesentlich elegantere Methode, weil hierbei die logisch zueinander gehörenden Funktionen auch auf dieselbe Weise angesprochen werden können. Aber es gibt noch ein weiteres Argument, warum xVT52 dem “gewöhnlichen” VT52 überlegen ist: die Geschwindigkeit. Diese liegt nämlich im Vergleich zum “Alten” um mindestens Faktor zwei höher; in Spezialfällen kann die Ausführung auch sechsmal und mehr beschleunigt werden. Um diese Steigerungen zu erreichen, wurden einige programmtechnische Tricks angewandt, die ich Ihnen nicht vorenthalten und zur Nachahmung empfehlen möchte. Aber erst das nächste Mal; jetzt möchte ich Ihnen zunächst einmal zeigen, wie schnell er denn nun wirklich ist, der erweiterte Emu.

Vorher/nachher und die Benchmarks

Wann immer es darum geht, Programme und deren Leistung zu objektivieren, geht es früher oder später um deren Geschwindigkeit. So auch diesmal. Auch wenn man nicht zu den “Geschwindigkeits-Fanatikern” gehört, die Leistungen (nur noch) nach der Anzahl der gewonnenen Nanosekunden beurteilen, so muß man sich doch darüber im klaren sein, daß eine tausendmalige Einsparung einer Millisekunde bereits eine Sekunde ausmacht und sich somit viele Wenig zu einem Viel aufsummieren. Wer fast ausschließlich die grafische Benutzeroberfläche und sonstige GEM-Programme benutzt, wird sich über die teilweise drastischen Geschwindigkeitszunahmen durch den Blitter-Einsatz freuen, obwohl dieser in einzelnen Funktionsaufrufen “nur unwesentlich” schneller als die softwaremäßigen Emulationen ist. Umgekehrt gibt es aber auch viele ST-Besitzer, die die grafischen Fähigkeiten zwar nicht verachten, aber aus Gründen der Effizienz lieber mit TOS-Programmen und Kommando-Interpretern arbeiten. Da in diesen Programmen der Bildschirmausgabe per VT52-Emulator eine tragende Rolle zukommt, spielt es hier sehr wohl eine Rolle, ob ein Bildschirm nun in einer oder in einer fünftel Sekunde aufgebaut ist, da sich dieser Vorgang leicht ein paarhundertmal in der Stunde wiederholen kann. Und eine durchschnittliche “Session” kann viele Stunden dauern...

Um nun also ein Maß für die Ausführungsgeschwindigkeiten zu bekommen, mußte wieder einmal einer der so beliebten Benchmarks herhalten. Für das Messen der Bildschirmausgabe gelten zwei Tests als Standard (zumindest für die IBM-Welt), so daß ich sie auch für den xVT52 verwendet habe (s. Listing 3). Es galt zehn volle Bildschirmseiten ohne und mit Scrollen auszugeben - bloß: wie? Denn mit Benchmarks ist das immer so eine heikle Geschichte. Wenn man nicht aufpaßt, mißt man mit diesen Tests alle möglichen Nebeneffekte - nur halt eben nicht das, was man eigentlich wollte! Tatort: GFA-BASIC. Wenn man hier mißt, wie lange es dauert, um 250 Zeilen mittels PRINT -Anweisung auszugeben, wird man lediglich herausbekommen, wie fix der BIOS-Traphandler ist. Grund: Frank Ostrowski hat schon früh erkannt, daß das BIOS wesentlich hurtiger arbeitet als die GEMDOS-Funktionen, in denen u.a. auf Ausgabeumlenkung geprüft wird, bevor dann am Ende ohnehin BIOS- oder XBIOS-Funktionen aktiviert werden. So wird also alles, was hinter dem PRINT-Befehl steht, Zeichen für Zeichen direkt über das BIOS ausgegeben. Also mußte ich direkt die GEMDOS-Funktion PRINT LINE (0x9) ansprechen, die ja einen beliebig langen String ausgeben kann und dabei nur einmal den Trap-Handler zu durchlaufen braucht. Somit konnte gewährleistet werden, daß nur die Ausgabegeschwindigkeiten und nicht die Nebeneffekte (inklusive der Interpretergeschwindigkeit) gemessen wurden.

Wow!

Ich glaube, daß man mit den Zeiten recht zufrieden leben kann. Es sind zwar keine Tempus-Geschwindigkeiten erreicht worden (was für ein Betriebssystem zugegebenermaßen auch unmöglich ist), aber dennoch liegen die Zeiten von xVT52 deutlich unter denen des Original-Emulators. In Tabelle 2 können Sie die Ergebnisse für die verschiedenen ST-Konfigurationen nachlesen. Je nach TOS-Version und Blitter-Einsatz liegt der Geschwindigkeitszuwachsfaktor zwischen zwei und fünf. Im “normalen” Einsatz ist eine Steigerung um das zwei- bis dreifache als realistisch zu betrachten. Natürlich werden mir jetzt einige Leser vorwerfen, daß die Benchmark-Tests alles andere als objektiv sind, weil die verwendete Funktion des GEMDOS so umgeschrieben wurde, daß sie die Strings nun “am Stück” und somit ohne weitere BIOS-Aufrufe ausgeben kann. Aber: Ist es denn nicht gerade Sinn und Zweck von (System-) Patches, Mängel in Programmen auszuräumen (auch mangelnde Geschwindigkeit)? Und: Die meisten TOS-Programme benutzen genau diese Funktion, um ihre Ausgaben auf den Bildschirm zu realisieren und kommen somit auch in den Genuß des erweiterten Emulators. Und schließlich hat der xVT52 ja auch noch ganz andere Qualitäten und Vorzüge...

Soweit für diesmal. Ich darf natürlich auch heute nicht versäumen, Ihnen viel Spaß (und wenig Blasen) beim Abtippen des Listings zu wünschen. Vielleicht hat der eine oder andere Tippfaule unter der Leserschar mit Blick auf Benchmarks und ESCapes nun doch noch Appetit bekommen. Würde mich natürlich freuen...

MS

; *************************************
; * >EXTENDED VT52-TERMINAL EMULATOR< * 3. Teil
; *===================================*
; * Entwickelt mit PROFIMAT-Assembler *
; *       M.Schumacher, (p) 12/87     *
; *************************************


SCR_LEFT:               ; ESC ’u'
    move.l  6(a4),a1        ; abs. Cursorposition
    suba.w  (a0),a1         ; -akt, Spalte=AZeilenanfang
    move.w  36(a0),d2       ; Zeichenhöhe (Pixels)
    subq.w  #1,d2           ; in dbra-Zähler wandeln

\lp1:
    movea.l a1,a2           ; a1=Ziel, a2=Quelle
    addq.w  #1,a2           ; Quelle=Ziel+1
    moveq   #12,d1          ; 13*6=78 Bytes verschieben
    move.b  (a1),d3         ; vorderstes Bytes merken
\lp2:
    move.b  (a2)+,(a1)+     ; 6 Bytes nach links verschieben
    move.b  (a2)+,(a1)+
    move.b  (a2)+,(a1)+
    move.b  (a2)+,(a1)+
    move.b  (a2)+,(a1)+
    move.b  (a2)+,(a1)+
    dbra    d1,\lp2
    move.b  (a2),(a1)+      ; Byte 78 verschieben
    move.b  d3,(a1)+        ; Anfangsbyte ans Ende (79) setzen
    dbra    d2,\lp1         ; nächste Pixelzeile verschieben
    rts

SCR_RIGHT:                  ; ESC 'x'
    move.l  6(a4),a1        ; abs. Cursorposition
    suba.w  (a0),a1         ; -akt. 5palte=^Zeilenanfang
    adda.w  38(a0),a1       ; +Bytes/Textzeile=^Anfang nächster Textzeile 
    move.w  36(a0),d2       ; Zeichenhöhe (Pixels)
    subq.w  #1,d2       ; in dbra-Zähler wandeln
\lp1:
    movea.l a,a2            ; a1=Ziel, a2=Quelle
    subq.w  #1,a2           ; Quelle=Ziel-1
    moveq   #12,d1          ; 13*6=78 Bytes verschieben
    move b  (a2),d3         ; letztes Bytes merken
\lp2:
    move.b  -(a2),-(a1) ;   6 Bytes nach rechts verschieben
    move.b  -(a2),-(a1)
    move.b  -(a2),-(a1)
    move.b  -(a2),-(a1)
    move.b  -(a2),-(a1)
    move.b  -(a2),-(a1)
    dbra    d1,\lp2
    move.b  -(a2),-(a1)     ; Byte 78 verschieben
    move.b  d3,-(a1)        ; letztes Byte an den Anfang setzen
    dbra    d2,\lp1         ; nächste Pixelzeile verschieben
    rts

SAVE_CRS:                   ; ESC 'j'
    move.w  10(a0),d0       ; Anzahl gespeicherter Positionen
    cmpl.w  #3,d0           ; schon alle 3 besetzt?
    bne.s   \set            ; nein
    subq.w  #1,d0           ; sonst 1 abziehen
    subq.w  #1,10(a0)
    move.l  16(a0),12(a0)   ; neue Position 1=alte P. 2 (alte 1 geht verloren) 
    move.l  20(a0),16(a0)   ; neue Position 2-alte P. 3 
\set:
    lsl.w   #2,d0           ; Anzahl in Longpointer wandeln
    move.l  (a0),12(a0,d0.w); Spalte und Zeile merken 
    addq.w  #1,10(a0)       ; Anzahl inkrementieren
    rts                     ; fertig

REST_CRS:                   ; ESC 'k'
    move.w  10(a0),d0       ; Anzahl gespeicherter Positionen
    beq.s   \zurück         ; fertig, falls 0
    subq.w  #1,d0           ; sonst Anzahl dekrementieren
    subq.w  #1,10(a0)
    lsl.w   #2,d0           ; und in Longpointer Handeln
    move.l  12(a0,d0.w),(a0); alte Position zurückholen 
    move.w  6(a0),d0    ; größte Zeilennummer
    cmp.w   2(a0),d0    ; < aktueller? (Hegen EVTL.Fontwechsel) 
    bpl.s   \zurück     ; nein, alles ok
    move.w  d0,2(a0)    ; sonst in letzte Zeile positionieren
\zurück:
    rts

GET_TCB:                    ; ESC 'g'
    bsr     UPDATE_END      ; Cursor wieder freigeben
    move.l  a0,d0           ; d0:=^TerminalControlBlock
    addq.l  #4,a7           ; und direkt zum Trap-Handler zurückspringen 
    rts                     ; (also nicht über ESC_SEQ)

GET_CCB:                    ; ESC 'h'
    bsr     UPDATE_END      ; Cursor wieder freigeben
    move.l  a4,d0           ; d0:=^CursorControlBlock
    addq.l  #4,a7           ; und direkt zum Trap-Handler zurückspringen 
    rts                     ; (also nicht über ESC_SEQ)

WHITE_BLK:                  ; ESC 'm'
    clr.w   VID_R0          ; inverse Darstellung (weiß auf schnarz)
    rts

BLK_WHITE:                  ; ESC 'n'
    move.w  #1,VID_R0       ; normale Darstellung (schwarz auf weiß)
    rts

DEL_TO_CRS:                 ; ESC 'd'
    move.w  2(a0),d0        ; akt. Zeile
    beq.s   L_TO_CRS        ; falls in Zeile 0, nur noch bis Cursor löschen 
    move.w  36(a0),d1       ; Zeichenhöhe in Pixeln
    subq.w  #8,d1           ; - 8
    beq.s   \del            ; 8x8-Font aktiv?
    add.w   d0,d0           ; sonst doppelt so viele Zeilen löschen
\del:
    subq.w  #1,d0           ; Anzahl Zeilen in dbra-Zähler Handeln
    move.l  (a0),-(a7)      ; akt. Spalte und Zeile retten
    pea     \weiter         ; Rücksprungadresse merken
    movem.l a0/a6,-(a7)     ; Register retten
    movea.l 6(a4),a6        ; abs. Cursorposition
    suba.w  (a0),a6         ; - akt. Spalte=^Zeilenanfang
    bra     CLS_ENTRY       ; Zellen löschen
\weiter:
    move.l  (a7)+,(a0)      ; Spalte und Zeile zurück

L_TO_CRS:                   ; ESC 'o'
    move.w  (a0),d1         ; akt. Spalte
    move.w  d1,d2           ; merken
    moveq   #80,d3          ; Bytes/Pixelzeile
    move.l  6(a4),a1        ; abs. Cursorposition
    suba.w  d1,a1           ; -akt. Spalte=Zeilenanfang
    movea.l a1,a2           ; merken
    adda.l  d3,a2           ; und Offset für nächste Pixelzeile addieren 
    move.w  36(a0),d0       ; Zeichenhöhe
    subq.w  #1,d0           ; in dbra-Zähler Handeln
\lp_pixz:
    clr.b   (a1)+           ; Byte löschen
    dbra    d1,\lp_pixz     ; nächstes Byte
    movea.l a2,a1           ; Anfang der nächsten Pixelzeile
    adda.l  d3,a2           ; nieder Offset addieren
    move.w  d2,d1           ; Zähler nieder laden
    dbra    d0,\lp_pixz     ; und nächste Pixelzeile löschen
\zurück:
    rts

GRAPHMODE:                  ; ESC 'r'
    lea     VEC_BASE,a1     ; Ausgabevektor
    lea     GET_WIDTH,a2    ; auf Holen der Grafikbreite
    move.l  a2,(a1)         ; umbiegen
    st      9(a0)           ; Grafikmodus einschalten
    rts

GET_WIDTH:                  ; Grafikbreite für ESC 'r' bestimmen
    lea     GET_HEIGHT_H,a2 ; Ausgabevektor auf Holen der Grafikhöhe 
    move.l  a2,(a1)         ; umbiegen
    lea     TCB,a0          ; ^TerminalControlBlock
    subi.w  #32,d0          ; Offset von Wert für Breite abziehen
    move.w  d0,44(a0)       ; und merken
    moveq   #80,d1          ; max. Spalte
    sub.w   (a0),d1         ; -akt. Spalte
    move.w  d1,40(a0)       ; als erlaubte Breite übernehmen
    rts

GET_HEIGHT_H:   ; Grafikhöhe, Hunderter
    lea     GET_HEIGHT_L,a2 ; Ausgabevektor auf Holen der Grafikhöhe 
    move.l  a2,(a1)         ; umbiegen
    lea     TCB+46,a0       ; Speicher für Grafikhöhe in Pixels
    subi.w  #32,d0          ; Offset vom Höhenwert abziehen
    mulu    #100,d0         ; mal 100
    move.w  d0,(a0)         ; merken
    rts

GET_HEIGHT_L:               ; Grafikhöhe, Zehner+Einer
    lea     TCB,a0          ; ^TerminalControlBlock
    subi.H  #32,d0          ; Offset vom Höhenwert abziehen
    move.w  d0,d1           ; und zwischenspeichern
    andi.w  #$F,d1          ; Einer isolieren
    lsr.w   #4,d0           ; Zehner
    andi.n  #$F,d0          ; isolieren
    mulu    #10,d0          ; mal 10
    add.w   d1,d0           ; + Einer
    add.w   d0,46(a0)           ; + Hunderter
    tst.w   44(a0)          ; Breite=0?
    bne.s   \weiter         ; nein
\fail:
    lea     STD_VEC,a2      ; sonst Ausgabe wieder auf Standard
    move.l  a2,(a1)         ; umbiegen
    rts
\weiter:
    tst.w   46(a0)          ; Höhe=0?
    beq.s   \fail           ; ja. abbrechen
    lea     GRAPHICS,a2     ; sonst Ausgabe auf Grafik
    move.l  a2,(a1)         ; stellen
    clr.l   48(a0)          ; Zähler initialisieren
    lea     CCB,a4          ; ^CursorControlBlock
    move.l  6(a4),52(a0)    ; abs. Cursorpos. als linken Offset merken 
    move.w  #400,d1         ; max. Anzahl Pixelzeilen
    move.w  2(a0),d2        ; akt. Zeile
    mulu    36(a0),d2       ; 4 Zeichenhöhe in Pixels
    sub.w   d2,d1           ; von max. Anzahl subtrahieren
    move.w  d,42(a0)        ; * Anzahl erlaubter Pixelzeilen
    rts
GRAPHICS:                   ; Grafikausgabe
    lea     TCB,a0          ; TerminalControlBlock
    move.w  48(a0),d1       ; akt. hör. Position
    move.w  50(a0),d2       ; akt. Pixelzeile
    cmp.w   42(a0),d2       ; mit erlaubter Anzahl vergleichen
    bpl.s   \next           ; zu groß, nicht ausgeben
    cmp.w   40(a0),d1       ; hör. Pos. mit max. Position vergleichen
    bpl.s   \next           ; zu groß, nicht ausgeben
    move.l  52(a0),a2       ; linker Zeilenoffset
    move.b  d0.0(a2,d1.w)   ; Grafikbyte ausgeben
    btst    #0,8(a0)        ; Invertierung eingeschaltet?
    beq.s   \next           ; nein
    not.b   0(a2,d1.w)      ; sonst halt invertieren
\next:
    addq.w  #1,d1           ; hör. Position ++
    cmp.w   44(a0),d1       ; mit Breite vergleichen
    beq.s   \more           ; letzte Position schon erreicht
    move.w  d1,48(a0)       ; sonst merken
    rts                     ; fertig
\more:
    clr.w   48(a0)          ; nieder bei Position 0 anfangen
    addi.l  #80,52(a0)      ; Offset für nächste Pixelzeile addieren 
    move.w  50(a0),d1       ; akt. Pixelzeile
    addq.w  #1,d1           ; ++
    cmp.w   46(a8),d1       ; =Höhe der Grafik?
    beq.s   \fertig         ; ja, Grafikmodus beenden
    move.w  d1,50(a0)       ; sonst neue Position merken
    rts
\fertig:
    lea     STD_VEC,a2      ; Ausgabe wieder auf Standard
    move.l  a2,(a1)         ; umbiegen
    sf      9(a0)           ; und Grafik ausschalten
    rts

CRS_UP:                     ; ESC 'A'
    tst.w   2(a0)           ; Zeile 0?
    beq.s   \zurück         ; ja, ignorieren
    subq.w  #1,2(a0)        ; sonst --
\zurück:
    rts

CRS_DOWN:                   ; ESC 'B'
    move.w  2(a0),d0        ; akt. Zeile
    cmp.w   6(a0),d0        ; =letzte?
    bpl.s   \zurück         ; ja, ignorieren
    addq.w  #1,2(a0)        ; sonst ++
\zurück:
    rts

CRS_RIGHT:                  ; ESC 'C'
    cmpi.w  #79,(a0)        ; letzte Spalte?
    bpl.s   \zurück         ; ja, ignorieren
    addq.w  #1,(a0)         ; sonst ++
\zurück:
    rts

CRS_UP_SCR:                 ; ESC 'I'
    tst.w   2(a0)           ; Zeile 0?
    bne.s   Nrunter         ; nein
    move.w (a0),-(a7)       ; akt. Spalte merken
    bsr     SCROLL_DOWN     ; sonst runterscrollen
    bsr     DEL_LINE        ; und Zeile löschen
    move.w  (a7)+,(a0)      ; akt. Spalte zurück
    bra s   \zurück
\runter:
    subq.w  #1,2(a0)        ; Zeile --
\zurück:
    rts

BIG_FONT:                   ; ESC 'F'
    move.l  24(a0),d0       ; ^8x16-Fontdaten
    cmp.l   32(a0),d0       ; Font schon aktiu?
    beq.s   \zurück         ; ja, fertig
    move.l  d0,32(a0)       ; sonst als aktuellen Font übernehmen
    move.w  #24,6(a0)       ; max. Zeile=24
    move.l  #$100500,36(a0) ; Höhe=16 Pixel: Bytes/Textzeile=1280 
    lsr 2(a0)               ; akt. Zeile halbieren
    clr.w   10(a0)          ; gemerkte Cursorpositionen löschen
\zurück:
    rts

SML_FONT:                   ; ESC 'G'
    move.l  28(a0),d0       ; ^8x8-Fontdaten
    cmp.l   32(a0),d0       ; Font schon aktiu?
    beq.s   \zurück         ; ja, fertig
    move.l  d0,32(a0)       ; sonst als aktuellen Font übernehmen
    move.w  #49,6(a0)       ; max. Zeile=49
    move.l  #$80280,36(a0)  ; Höhe=16 Pixel: Bytes/Textzeile=648 
    lsl     2(a0)           ; akt. Zeile uerdoppeln
    clr.w   10(a0)          ; gemerkte Cursorpositionen löschen
\zurück:
    rts

DEL_FROM_CRS:   S ESC J'
    move.w  6(a0),d0        ; letzte Zeile
    sub.w   2(a0),d0        ; Cursor schon unten?
    beq.s   L_TO_ENO        ; ja, nur noch ab Cursor löschen
    move.w  36(a0),d1       ; Zeichenhöhe in Pixeln
    subq.w  #8,d1           ; - 8
    beq.s   \del            ; 8x8-Font aktiu
    add.w   d0,d0           ; sonst doppelt so viele Zeilen löschen
\del:
    subq.w  #1,d0           ; in dbra-Zähler wandeln
    move.l  (a0),—(a7)      ; Spalte und Zeile retten
    pea     \weiter         ; Rücksprungadresse auf Stack
    movem.l a8/a6,-(a7)     ; Register retten
    move.l  LOGBASE,a6      ; ^Video-RAM
    adda.w  #32000,a6       ; ^Bildschirm-Ende
    bra     CLS_ENTRY       ; Zeilen löschen
\weiter:
    move.l  (a7)+,(a0)      ; Spalte und Zeile zurück

L_TO_END:                   ; ESC 'K'
    moveq   #79,d1          ; max. Spalte
    sub.w   (a0),d1         ; -akt. 5palte=Anzahl zu löschender Zeichen 
    move.w  d1,d2           ; merken
    moveq   #80,d3          ; Bytes/Pixelzeile
    move.l  6(a4),a1        ; abs. Cursorposition
    movea.l a1,a2           ; merken
    adda.l  d3,a2           ; und Offset für nächst Pixelzeile addieren 
    move.w  36(a0),d0       ; Zeichenhöhe
    subq.w  #1,d0           ; in dbra-Zähler wandeln

\lp_pixz:
    clr.b   (a1)+           ; Byte löschen
    dbra    d1,\lp_pixz     ; nächstes Byte
    movea.l a2,a1           ; Anfang der nächsten Pixelzeile
    adda.l  d3,a2           ; wieder Offset addieren
    move.w  d2,d1           ; Zähler wieder laden
    dbra    d0,\lp_pixz     ; und nächste Pixelzeile löschen
\zurück:
    rts

DEF_TABS:                   ; ESC 'N'
    lea     TABS,a0         ; ^Bitvektor für Tabulatoren
    move.l  #$80808080,d0   ; Bitmaske: Jede 8. Position
    move.l  d0,(a0)+        ; Tabulatoren setzen
    move.l  d0,(a0)+
    move.w  d0,(a0)
    rts

CLR_TABS:                   ; ESC 'Q'
    lea     TABS,a0         ; Miami weiß
    clr.l   (a0)+           ; Tabulatoren löschen
    clr.l   (a0)+
    clr.w   (aB)
    rts

SET_TAB:                    ; ESC 'P'
    lea     VEC_BASE,a1     ; Vektor
    lea     SGET_TAB,a2     ; zum Holen der Position
    move.l  a2,(a1)         ; umbiegen
    rts                     ; fertig
SGET_TAB:
    lea     VEC_BASE,a1     ; Vektor wieder
    lea     STD_VEC,a2      ; auf normale Ausgabe
    move.l  a2,(a1)         ; umschalten
    bsr.s   CALC_TAB        ; Tab-Position bestimmen
    bset    d2.0(a0,d0.w)   ; und Tab setzen
\zurück:
    rts
CLR_TAB:                    ; ESC 'Q'
    lea     VEC_BASE,a1     ; Vektor
    lea     CGET_TAB,a2     ; zum Holen der Position
    move.l  a2,(a1)         ; umbiegen
    rts                     ; fertig
CGET_TAB:
    lea     VEC_BASE,a1     ; Vektor wieder
    lea     STD_VEC,a2      ; auf normale Ausgabe
    move.l  a2,(a1)         ; umschalten
    bsr.s   CALC_TAB        ; Bitvektorposition berechnen
    bclr    d2.0(a0,d0.w)   ; und Tab löschen
\zurück:
    rts

CALC_TAB:                   ; Tabulatorposition berechnen
    subi.w  #32,d0          ; Offset von Tabulatorposition abziehen
    bmi.s   \fail           ; zu klein, ignorieren
    cmpi.w  #80,d0          ; größer als größte Spalte?
    bpl.s   \fail           ; ja, ignorieren
    lea     TABS,a0         ; ^Tab-Vektor
    ext.w   d0              ; Position auf Wortlänge bringen
    move.w  d0,d1           ; und retten
    asr.w   #3,d0           ; durch 8 diuidieren
    moveq   #7,d2
    and.w   d2,d1           ; Rest isolieren
    sub.w   dl,d2           ; und Offset berechnen
    rts

\fail:
    addq.l #4,a7            ; direkt zum Trap-Handler
    rts                     ; zurückspringen

LIGHT_ON:                   ; ESC 'R'
    bset    #3,8(a0)        ; halbe Helligkeit einschaiten
    rts

LIGHT_OFF:                  ; ESC 'S'
    bclr    #3.8(a0)        ; intensive Darstellung einschalten
    rts

Es folgen noch zwei Listings auf der nächsten Seite.

Listing 2: So einfach kann man prüfen, welcher Emulator gerade aktiv ist (in GFA-BASIC).

' *****************************
' *  xVT52-INSTALLATIONSTEST  *
' *****************************
PRINT CHR$(27);         ! ESCape ausgeben
tcb=BIOS(3,2,ASC("g"))  ! Pointer auf TCB holen
PRINT CMR$(27);         ! ESCape ausgeben
ccb=BIOS(3,2,ASC("h"))  ! Pointer auf CCB holen
PRINT HEX$(tcb)'HEXS(ccb) ! Adressen ausgeben
IF (ccb-tcb)<>66        ! schon installiert?
    PRINT "Nur Original-UT52 installiert!"
ELSE
    PRINT CHRS(27);"R>>> xVT52 installiert! <<<";CHR$(27);"S"
ENDIF

Listing 3: So wurden die Ausgabezeiten ermittelt (in GFA-BASIC)

' ************************
' *  BENCHMARK-TEST FÜR  *
' *    xVTS2-EMULATOR    *
' ************************
'
b$=SPACE$(1999)+CHR$(8)     ! Platz schaffen
b=VARPTR(b$)                ! Adresse besorgen
FOR i=b 10 b+1998           ! Mit Zufalls-
    POKE i,32+RANDOM(224)   ! Zeichen belegen 
NEXT i
PRINT CHR$(27);"v"          ! Wrapping ein
'
' *** TEST 1 ***
t=TIMER
FOR i=1 TO 10               ! zehn Seiten
    CLS                     ! ausgeben
    VOID GEMDOS(9,L:VARPTR(b$))
NEXT i
t1=TIMER
PRINT
PRINT (t1-t)/200            ! Zeit ausgeben
REPEAT
UNTIL inkey$<>""
'
' *** TEST 2 ***
'
PRINT AT(1,24);             ! Cursor nach unten
t=TIMER
FOR i=1 TO 10               ! zehn Seiten
    VOID GEMDOS(9,L:VARPTR(b$))
NEXT i                      ! Scrollen
t1=TIMER
PRINT
PRINT (t1-t)/200            ! Zeit ausgeben


Links

Copyright-Bestimmungen: siehe Über diese Seite
Classic Computer Magazines
[ Join Now | Ring Hub | Random | << Prev | Next >> ]