FURWARE text/Developers/de
Diese Seite beinhaltet technische Informationen zu FURWARE text und richtet sich an Entwickler und interessierte Benutzer.
Tipp: Wenn du mehr zu einem bestimmten Thema wissen möchtest, hinterlasse bitte eine Notiz auf der Diskussionsseite. :) |
Terminologie und Hierarchien
Ein "Set" bezeichnet ein Text-Display, das aus in Zeilen und Spalten angeordneten Prims besteht. Jedes Set hat einen Namen, der in der Regel mithilfe des Display Creators gesetzt wird. Ein Linkset kann mehrere Sets enthalten.
Eine "Box" ist eine virtuelle Text-Box innerhalb eines Sets. Jedes Set hat immer eine (Root-)Box mit derselben Größe und demselben Namen wie das Set. Jeder Box wird beim Erstellen ein Name zugewiesen (per Parameter des fw_addbox-Befehls). Zu einem Set können mehrere Boxen hinzugefügt werden, die dann in Layern übereinandergelegt dargestellt werden (in der Reihenfolge, in der sie hinzugefügt wurden).
Daraus folgen einige Zusammenhänge:
- Eine Box ist immer Teil von einem Set und liegt vollständig innerhalb von diesem.
- Ein Set besteht aus einem oder mehreren Prims.
- Ein Prim kann mehrere Faces haben.
- Jedes Face stellt ein Zeichen des Texts dar.
Der Text-Inhalt einer Box wird oft als "data" der Box bezeichnet ("data" wird aber auch in allgemeinerem Zusammenhang verwendet).
Der Text-Stil einer Box (oder Textabschnitten) wird oft als "conf" (der Box) bezeichnet.
Datenstrukturen für Box-, Set- und Prim-Informationen
Die meisten Informationen zur Geometrie und zum Inhalt des Displays werden in einigen Listen gespeichert. Einige Listen enthalten auch Indizes ("Pointer") zu Daten in anderen Listen.
Box-Informationen
- "boxNameList" ist eine einfache Liste der Namen aller Boxen. Der Grund dafür, dass die Namen in einer separaten Liste vorgehalten werden (und nicht als Teil von "boxDataList", siehe unten) ist, dass die Namen schnell durchsuchbar sein sollen. Das ist beispielsweise nötig wenn neuer Text gesetzt werden soll und der Index einer Box ermittelt werden muss anhand ihres Namens.
- "boxDataList" ist eine Strided List, die mehr Informationen zu jeder Box enthält. Ein Stride enthält:
- Den Text der Box (string).
- Die Textstil-Einstellung der Box (string).
- Einen bit-packed integer, der den Layer der Box (4 Bits), den Index des Sets, zu dem die Box gehört (8 Bits), dirty flags (2 bits), zwei reservierte Bits und die "Layer Override"-Bits (16 Bits) enthält.
- Die Geometrie der box (Spalten-Offset, Zeilen-Offset, Breite, Höhe) (rotation).
Set-Informationen
- "setDataList" ist eine Strided List, die Informationen zu jedem Set enthält. Ein Stride enthält:
Prim-Informationen
- "primLinkList" enthält die Link-Nummern der einzelnen Prims, sortiert zuerst nach Set, dann nach Zeilen, dann nach Spalten. Die Link-Nummern sind nicht bit-packed, weil sie schnell durchsuchbar sein sollen, etwa wenn Touch Queries bearbeitet werden.
- "primFillList" enthält bit-packed Integer, die den Status der einzelnen Faces auf den Prims enthalten. "Der Status" bedeutet hier, ob die Faces momenten ein Leerzeichen anzeigen oder nicht. Diese Information wird als Cache benutzt um unnötige SetPrimitiveParams-Aufrufe zu vermeiden.
- "primLayerList" enthält bit-packed Integer, die die Zuordnung von Faces zu Layern beinhaltet. Die 32 Bits des Integers sind dabei in je 4 Bits für jedes Face geteilt und somit kann jedem Face eine von 16 Layer-IDs zugeteilt werden. Diese Information wird während des Renderns verwendet um schnell feststellen zu können, welcher Layer an der Position dieses Faces gerade im Vordergrund liegt.
Grundlegender Ablauf der Events
Das folgende Bild ist ein Versuch, die grundlegenden Zusammenhänge von Events und Funktionen im FURWARE text-Skript darzustellen, wenn ein Befehl empfangen wird, der den Inhalt oder das Layout des Displays verändert (eine deutsche Übersetzung folgt).
Die Initialisierungs-Routine
To do
Der link_message(...)-Handler
To do
Die setDirty(...)-Funktion
Diese Funktion steht zwischen dem Entgegennehmen von Befehlen in link_message(...) und dem tatsächlichen (Neu-)Zeichnen in refresh(...). Sie besitzt folgende Parameter:
integer action
Kann einen der Werte der Konstanten ACTION_CONTENT, ACTION_ADD_BOX oder ACTION_DEL_BOX annehmen. Dieser Wert gibt an, welche Art von Kommando ausgeführt werden soll (beispielsweise ist das Setzen von neuem Text von der Art ACTION_CONTENT). Der übergebene Wert wird als globale Variable gespeichert und beim nächsten Aufruf von refresh(...) wieder zurückgesetzt.
Der Grund dafür, dass wir uns diesen Wert merken, ist das zeitverzögerte Aktualisieren des Displays (welches im Übrigen einer der Gründe ist, weshalb setDirty(...) als Puffer zwischen Parsen und Refreshen überhaupt eingebaut wurde). Am Ende von setDirty(...) wird ein Timer mit einem recht kurzen Intervall gestartet. Das timer()-Event ruft dann lediglich refresh() auf. Allerdings können in dieser kurzen Zeit noch weitere link_message(...)-Events empfangen und verarbeitet werden, was uns erlaubt, mehrere Befehle zu sammeln bevor wir das Display tatsächlich aktualisieren (beispielsweise das Setzen von Text mehrerer verschiedener Boxen). Dies kann in manchen Situationen einen großen Geschwindigkeitsvorteil bringen. Beachte, dass der Timer nicht neugestartet wird, wenn die letzte Aktion bereits auf etwas anderes als 0 gesetzt ist. Das "garantiert" uns, dass das timer()-Event irgendwann ausgeführt wird, selbst wenn wir einen konstanten Strom von Befehlen empfangen.
Es gibt allerdings einen Haken an der Sache. Wenn wir alle verschiedenen Arten von Befehlen wie das Setzen von Text, Stilen, Variablen, das Hinzufügen und Entfernen von Boxen sammeln würden ohne zu geeigneten Zeitpunkten zu aktualisieren, würden wir irgendwann ein Durcheinander und kaputte Datenstrukturen haben (du darfst gerne über die Details nachdenken, aber es verursacht Kopfschmerzen). Hier kommt die Unterscheidung zwischen den verschiedenen Arten von Aktionen ins Spiel.
Operationen, die lediglich den "Inhalt" von Boxen verändern sind gewissermaßen "kompatibel" zueinander und können gesammelt werden, da die Indizes der Listen mit Box-Informationen und der Layer während der Änderungen konstant bleiben. Operationen, die Boxen hinzufügen können ebenfalls gesammelt werden, da die Informationen lediglich zu einigen Listen hinzugefügt werden und wir dann die neuen Boxen auf einmal aktualisieren können. Operationen, die Boxen löschen können gesammelt werden, da wir uns im Grunde nur merken müssen, welche Layer wieder freigeworden sind. Anschließend können wir durch die verbliebenen Boxen gehen und diese entsprechend neu zeichnen.
Da das Mischen der verschiedenen Arten von Operationen ungut ist, prüft der Handler wenn ein neuer Befehl hereinkommt, ob die neue Operation mit der vorherigen kompatibel ist (das heißt, "Art der letzten Operation" == "Art der neuen Operation") und wenn sie dies nicht ist, wird ein sofortiges Aktualisieren erzwungen bevor der Befehl verarbeitet wird. Beachte, dass nur die Art der Operation gleich sein muss, nicht der genaue Befehl; beispielsweise sind die Befehle fw_data und fw_conf zueinander kompatibel, da beide den Typ ACTION_CONTENT besitzen.
Beachte weiterhin, dass die Art der Operation die Arbeitsweise der refresh()-Funktion beeinflusst; bitte siehe den entsprechenden Abschnitt für Einzelheiten.
integer first / last
Das Intervall von Box-Indizes, auf denen gearbeitet werden soll. Dies erlaubt uns direkt auf einer ganzen Reihe von Boxen auf einmal zu arbeiten.
integer isConf
Kann entweder 0 oder 1 sein. Dieser Wert wird dazu benutzt um festzustellen, ob etwaig übergebene Daten als data oder conf der Box(en) gespeichert werden sollen. Außerdem wird das (Neu-)Zeichnen der Rahmen von Boxen erzwungen, wenn isConf 1 ist, da sich dieser geändert haben könnte.
integer newLayerOverrideBits
An dieser Stelle wird es ein wenig knifflig. :) Bevor wir uns um den Sinn dieses Wertes kümmern, hier einige Fakten: Dieser Parameter sollte nur Nicht-Null sein, wenn Boxen hinzugefügt oder entfernt werden. Der Wert wird bit-packed als Teil des Status der Boxen in boxDataList gespeichert und am Ende von refresh() wieder gelöscht. Der Wert selbst ist 16 Bits lang und jedes Bit steht für einen der 16 möglichen Layer innerhalb eines Sets. Beispielsweise bedeutet ein Wert von 0xFFFF (binär 1111111111111111) "alle Layer" während ein Wert von 0x4 (binär 0000000000000100) "Layer 2" bedeutet (von 0 beginnend gezählt).
Der Wert wird in der draw(...)-Funktion verwendet, welche an verschiedenen Stellen in refresh() benutzt wird um ein Zeichen auf eine Face zu zeichnen. Normalerweise holt sich draw(...) die ID des Layers, der sich an der Position des Face gerade im Vordergrund befindet, sowie die ID des Layers der Box, die gerade gezeichnet werden soll. Wenn diese Layer-IDs ungleich sind, dann wird die Face nicht verändert, da die zu zeichnende Box an der Stelle nicht im Vordergrund ist.
Wenn wir jetzt aber eine Box hinzufügen oder entfernen, dann kann sich die Information, welche Layer an welchen Stellen im Vordergrund sind, für einige Faces ändern. Das ist genau die Information, die uns die newLayerOverrideBits liefern. Wenn beispielsweise eine neue Box hinzugefügt wird, sind deren Override-Bits auf 0xFFFF gesetzt. Wenn diese Box nun gezeichnet wird, dann merkt die draw(...)-Funktion, dass sie alle anderen Layer überschreiben und die neue Box darüber zeichnen soll. Wenn eine Box entfernt wird ist die Logik ähnlich, allerdings werden nur bestimmte Bits gesetzt und die Reihenfolge des Refreshs wird umgedreht; siehe dazu auch den Abschnitt über die refresh()-Funktion.
integer setIndex
Erlaubt uns, das (Über-)Schreiben von Informationen auf Boxen eines bestimmten Sets einzuschränken. Ein Wert von -1 bedeutet, dass auf allen Sets gearbeitet werden soll.
integer withData
Gibt an, ob der "data"-Parameter (siehe unten) Daten enthält. Das erlaubt eine Unterscheidung zwischen dem leeren String und "keine Daten angegeben".
string data
Enthält entweder neuen Text oder neue Stil-Einstellungen zur Speicherung in boxDataList.
Die refresh()-Funktion
Die refresh()-Funktion kann als das "Herz" des Text-Skripts angesehen werden. Sie wird entweder aufgerufen nachdem einige Änderungen gesammelt wurden oder wenn eine Aktualisierung während des Abarbeitens eines Befehls erzwungen werden muss.
Sie besteht hauptsächlich aus einer großen Schleife, die über alle Boxen iteriert und deren "Dirty"-Bit prüft. Wenn eine Box als dirty markiert ist, werden ihre Informationen (Text, Stil, Geometrie) geladen und zum Zeichnen vorbereitet.
Ein paar Worte zur Reihenfolge der Schleife: Normalerweise werden die Boxen in der umgekehrten Reihenfolge, in der sie hinzugefügt wurden, durchgegangen. Das bedeutet, dass die Boxen "von oben nach unten" aktualisiert werden (bezüglich der Layer). Allerdings gibt es eine Ausnahme: Wenn die Art des letzten Befehls ACTION_ADD_BOX war (siehe auch den "action"-Parameter von setDirty(...)), dann werden die Boxen in der Reihenfolge, in der sie hinzugefügt wurden, durchgegangen ("von unten nach oben"). Dies ist nötig um die Information, welche Layer auf welchen Faces gerade im Vordergrund sind, korrekt zu aktualisieren (siehe auch den "newLayerOverrideBits"-Parameter von setDirty(...)).
Zurück zur Schleife und dem "Dirty"-Bit. Genauergesagt gibt es zwei "Dirty"-Bits: Eines sagt uns, ob die Box überhaupt aktualisiert werden soll und ein zweites, ob außerdem die Rahmen der Box u.U. aktualisiert werden müssen. Wenn etwa lediglich der Text einer Box geändert wurde – aber nicht die Stileinstellungen – dann gibt es keinen Grund, den Rahmen neuzuzeichnen.
Der Text der Box wird dann in Teile zerlegt (an den Stellen wo Leerzeichen, Umbrüche, Stil-Markup auftreten) und eine innere Schleife iteriert durch diese Teile, welche wiederum Listen von einzelnen Zeichen und Stilinformationen, die direkt im Text gegeben sind, vorbereitet. Sobald eine Zeile an Daten vorbereitet ist (eine Zeile bedeutet hier eine Zeile von Prims), kümmert sich eine weitere kleine Schleife um das eigentliche Zeichnen auf die Faces. Anschließend wird mit der nächsten Zeile fortgefahren.
Nachdem eine Box aktualisiert wurde, werden ihre "Dirty"-Bits zurückgesetzt. Sobald alle Boxen fertig sind, wird die global gespeicherte Art der letzten Aktion zurückgesetzt und (falls die "Notification"-Option aktiviert ist) eine "fw_done"-Nachricht an das Linkset gesendet.
Caching-Strategien
To do