BlitzBasicScript

Kommentare anzeigen Worklog abonnieren
Gehe zu Seite Zurück  1, 2

Worklogs BlitzBasicScript

Ideen und Konzepte

Freitag, 11. Januar 2019 von Spark Fountain
Da BBScript keine 1:1 Kopie von BlitzBasic werden kann, habe ich mir einige Änderungen überlegt bzgl. der Syntax sowie des Befehlsumfangs. Folgende Entscheidungen stehen bereits fest:

-Goto und Gosub werden nicht unterstützt (weil böse)
-es wird schwach und dynamisch typisiert (für Anfänger aus meiner Sicht leichter)
-Netzwerkbefehle werden vorerst nicht implementiert (weil zu aufwändig und sicherheitskritisch - danke @Thunder für diese Gedankenanregung)

Des weiteren könnte ich mir bestimmte Anpassungen vorstellen, bei denen ich aber noch nicht sicher bin, ob sie unbedingt nötig bzw. sinnvoll sind:

-der Vergleichsoperator wird von = zu == verändert, um mehr Eindeutigkeit zu haben
-dim-Arrays müssen keine feste Größenangabe mehr bekommen (weil JavaScript sowieso dynamische Arrays beherrscht)

Was haltet hier von diesen Anpassungen? Anregungen und Kritik sind sehr willkommen.

Die verflixte Hauptschleife

Montag, 7. Januar 2019 von Spark Fountain
Ein Problem hat mich schon vor einem halben Jahr beschäftigt, nämlich die adäquate Umsetzung einer Hauptschleife in BBScript. Warum ist das ein Problem? Weil JavaScript-Code nur eventbasiert ausgeführt wird und nicht dauerhaft "am Stück" laufen kann. Der Browser kann nichts anderes tun, während JavaScript-Code abgearbeitet wird, sondern muss so lange warten, bis er wieder "Leerlauf" hat, um dann z. B. den Bildschirm neu einzuzeichnen.

Meine Mission ist es, möglichst viel BB-Code mit BBScript wiederverwenden zu können und da dürfen nicht einfach (Haupt-)schleifen fehlen. Allerdings kann ich den Code auch nicht 1:1 in TypeScript (bzw. JavaScript) nachgenerieren, weil dies eben genau in endlosen Ausführungen gipfeln würde. Auch das asynchrone Abarbeiten der einzelnen Statements würde hier nicht viel nützen: Selbst wenn man die Reihenfolge beibehält, gibt es ja nach jedem Statement immer unmittelbar ein neues zu starten...

Mit ein bisschen Googeln habe ich diesen interessanten Beitrag zum Thema "Gaming Loops and Timing" gefunden, wo insbesondere die JavaScript-Funktion requestAnimationFrame näher beschrieben wird. Damit kann man genau das erreichen, was eine Hauptschleife tun soll: In bestimmten definierten Zeitintervallen eine Menge von Statements regelmäßig abfrühstücken.

Code: [AUSKLAPPEN]
var lastFrameTimeMs = 0, // The last time the loop was run
    maxFPS = 10; // The maximum FPS we want to allow
 
function mainLoop(timestamp) {
    // Throttle the frame rate.   
    if (timestamp < lastFrameTimeMs + (1000 / maxFPS)) {
        requestAnimationFrame(mainLoop);
        return;
    }
    lastFrameTimeMs = timestamp;
}

// Start things off
requestAnimationFrame(mainLoop);


Schön und gut - aber wie bastelt man jetzt aus einer BB-Hauptschleife eine BBScript-Hauptschleife? Es gibt keine explizite Definition, dass eine bestimmte Schleife "die Hauptschleife" des Programms ist. Zwar wird es aus dem Programmkontext für einen Programmierer meist leicht ersichtlich, aber technisch gesehen ist die Hauptschleife nichts weiter als eine Schleife, deren Bedingung jedes mal wahr ist, sodass sie erneut ausgeführt wird. Theoretisch sind auch mehrere "Hauptschleifen" in einem Programm denkbar.

Mein erster Ansatz war, durch Evaluation der Wahrheitswerte aller Schleifenbedingungen quasi herauszufinden, welches die Hauptschleife ist. Aber dieser Ansatz ist bei näherer Betrachtung zum Scheitern verurteilt:

BlitzBasic: [AUSKLAPPEN]
Global state$ = "input user name"

While(state$ <> "quit")
Select state$
Case "input user name"
Repeat
Local asciiCode = GetKey()
If asciiCode = 13 Then ; wenn Enter gedrückt: wechsele ins Menü
state$ = "menu"
Exit
EndIf
Forever
Case "menu"
; ...
Case "game"
; ...
Case "quit"
Exit
End Select
Wend
End


Zugegeben, das Beispiel ist relativ konstruiert, weil die innere Repeat-Forever-Schleife aufgrund der äußeren While-Schleife redundant ist. Trotzdem verdeutlicht das Beispiel relativ gut das Problem: Eine automatische Hauptschleifen-Erkennung könnte zu dem Schluss gelangen, dass selbstverständlich die Repeat-Forever-Schleife die Hauptschleife sei, da die While-Schleife eine dynamische Bedingung besitzt und deshalb scheinbar nur vorübergehende Bedeutung hat.

Man könnte nun sagen, dann dürfen eben nur Schleifen auf der äußersten Ebene als Hauptschleifen infrage kommen. Aber auch das wäre nur eine ungeprüfte Konvention, und auch wenn mir spontan keine Situation einfällt, in der die Hauptschleife "verschachtelt" wäre, so ist es trotzdem nicht unmöglich, ein derartiges Programm zu schreiben, das dann nicht korrekt in BBScript abgebildet werden würde.

Langer Rede kurzer Sinn: Es wird mit BBScript ein neues Schlüsselwort eingeführt: MainLoop. Dieses dient dem Parser dazu, einen Codeblock als Hauptschleife eindeutig zu identifizieren und diesen (nach BBScript umgewandelten) Code dann in die oben beschriebene JavaScript-Methode requestAnimationFrame einzufügen:

BlitzBasic: [AUSKLAPPEN]
MainLoop
Select state$
Case "input user name"
; ...
Case "menu"
; ...
Case "game"
; ...
Case "quit"
Exit
End Select
End MainLoop


Natürlich ist diese Lösung mit zwei Einschränkungen für alte BB-Programme verbunden:
Arrow Es kann nur genau eine Hauptschleife im Programm geben, welche sich wie Repeat-Forever verhält
Arrow Alter BB-Code muss in Bezug auf die Hauptschleifen-Definition umgeschrieben werden

Da das Umschreiben einer Hauptschleife aus meiner Sicht nicht besonders zeit- und programmieraufwändig ist, lässt sich dieser Kompromiss sicherlich verkraften.

Es werde Sound

Sonntag, 6. Januar 2019 von Spark Fountain
Die wichtigsten (2D-)Soundbefehle sind nun implementiert. Obwohl die Web Audio API sehr umfangreich und ausgetüftelt ist, habe ich mich doch gewundert, wie lange es gedauert hat, die zwei relativ simplen Befehle SoundPan und SoundPitch umzusetzen.

Das native HTMLAudioElement unterstützt nicht besonders viele Funktionen, lediglich die Lautstärke kann direkt angepasst werden. Darum musste ich mir die Dokumentation von AudioContext aneignen, um schlussendlich das per HTTP-Request geladene Soundfile in einen solchen Kontext zu laden und die Samples mithilfe von decodeAudioData in einen Buffer zu laden. Web Audio nutzt verschiedene "Knoten", die miteinander verknüpft werden können, um mehrere Audio-Effekte hintereinander zu schalten. Als Effektknoten gibt es für BBScript einen StereoPanner, mit dem die Balance geregelt werden kann, sowie einen VolumeGain für laut und leise. Meine Audio-Knoten-Kette sieht dann so aus:

AudioBuffer -> StereoPanner -> VolumeGain -> Sounddatei

Für SoundPitch musste ich eine Anpassung der Samplerate implementieren. Das war echt ein harter Brocken und ich habe stundenlang nach einem einfachen Codebeispiel gesucht, aber einfach nichts Simples und Brauchbares gefunden... Zwischendurch wollte ich schon eine Audio-Resampler-Library benutzen, die aber auch nicht funktioniert hatte. Evil or Very Mad Schlussendlich habe ich anhand verschiedener kleiner Beispiele kapiert, dass man einen neuen Buffer erstellen und ihm eine explizite Samplerate zuweisen kann. Daraufhin muss man "nur" die Bytes aus den verschiedenen Kanälen das Original-Buffers kopieren und in den neuen Buffer einfügen - et voila: Fertig ist das Resampling! Very Happy

Als nächstes werde ich überlegen, welche Unterschiede es zu den Befehlen ChannelVolume, ChannelPitch und ChannelPan gibt und ob ich der Einfachheit halber dieselben Implementierungen verwende, oder eine Unterscheidung zwischen Sounddatei und Kanal vornehme. Letzteres ist wahrscheinlich "richtiger", weil verschiedene Kanäle dazu genutzt werden könnten, ein und dieselbe Quell-Audio-Datei mit unterschiedlichen Effektkombinationen abzuspielen.

Ein paar technische Einblicke

Freitag, 4. Januar 2019 von Spark Fountain
An dieser Stelle möchte ich noch ein paar technische Details zur bisherigen Implementierung von BlitzBasicScript (kurz BBScript) aufzeigen.

Befehle
Nahezu sämtliche Befehle aus B+ und B3D sollen in BBScript nachgebaut werden, sofern das technisch möglich ist (Einschränkungen gibt es bei Befehlen, die nativen C++ Code ausführen bzw. auf Betriebssystemfunktionen zugreifen müssen).

Um die Befehle einigermaßen sauber zu strukturieren, habe ich mich an die Kategorisierung der BlitzForum-Onlinehilfe gehalten. Für jeden BB-Befehl existiert schlussendlich eine individuelle Implementierung, die theoretisch auch austauschbar ist. Somit könnte man zu einem späteren Zeitpunkt z. B. die 3D-Engine austauschen, wenn sie nicht mehr weiterentwickelt werden sollte.

Folgende 87 Befehle sind bereits korrekt implementiert:
Code: [AUSKLAPPEN]

Grundlagen:
-DebugLog
-Abc
-ACos
-ASin
-ATan
-ATan2
-Bin
-Ceil
-Cos
-Exp
-Float
-Floor
-Hex
-Int
-Log
-Log10
-Pi
-Sar
-Sgn
-Shl
-Shr
-Sin
-Sqr
-Tan
-Asc
-Chr
-Instr
-Left
-Len
-Lower
-LSet
-Mid
-Replace
-Right
-RSet
-Str
-String
-Trim
-Upper
-CurrentDate
-CurrentTime
-Delay
-Rand
-Rnd

2D Grafik:
-GfxModeDepth
-GfxModeExists
-Graphics
-GraphicsDepth
-GraphicsHeight
-GraphicsWidth
-Cls
-ClsColor
-Color
-Line
-Origin
-Oval
-Rect
-AutoMidHandle
-CopyImage
-CreateImage
-DrawBlock
-DrawImage
-FreeImage
-HandleImage
-ImageHeight
-ImagesOverlap
-ImageWidth
-ImageXHandle
-ImageYHandle
-LoadImage
-MaskImage
-MidHandle
-RectsOverlap
-ResizeImage
-ScaleImage
-ColorBlue
-ColorGreen
-ColorRed
-Plot

3D Grafik
-CameraClsColor
-CreateCamera
-MoveEntity
-PositionEntity
-CreateCube
-CreateSphere
-CreateCylinder
-CreateCone



Datenströme
Wie schon im ersten Worklog-Eintrag erwähnt, kann man aus Sicherheitsgründen nicht aus einem Webbrowser heraus Dateisystem-Inhalte des Clients auslesen bzw. Daten irgendwo hin schreiben. Deshalb benötigt man einen (kleinen) Web-Server, der diese Aufgabe übernimmt. Ich werde ein PHP-Skript schreiben, welches einen eindeutigen Dateipfad übergeben bekommt und dann den Dateiinhalt als Klartext bzw. Base64 als HTTP-Response übermittelt - je nachdem ob es sich um ein Textdokument (z. B. JSON, TXT) oder ein Multimedia-Dokument (z. B. MP3, PNG) handelt.

Bei der Anwendung von Read- bzw. Write-Befehlen wird es etwas knifflig. Ein kleiner Vergleich zwischen BlitzBasic und BBScript soll das verdeutlichen:
-Bei BB liefert ReadFile bzw. WriteFile ein Handle auf die entsprechende Datei zurück, und man kann dann nach und nach die Datei auslesen bzw. beschreiben. Außerdem lässt sich einfach an eine bestimmte Dateiposition springen und dort weitermachen. Wenn man fertig ist, wendet man CloseFile an und das Datei-Handle wird gelöscht.
-Bei BBScript werden (wie zuvor beschrieben) alle Daten über HTTP hin- und hergeschickt. Allerdings müssen alle Befehle sequentiell nacheinander abgearbeitet werden, sodass für jede Lese- und Schreiboperation ein neuer HTTP-Request anfallen würde. Bei vielen ReadLine- bzw. WriteLine-Aufrufen könnte das schnell zu einem tierischen Overhead ausufern. Außerdem könnte ein spürbarer Delay einsetzen, weil so ein HTTP-Aufruf von der Anfrage bis zur erhaltenen Antwort viel länger braucht als ein simpler Zugriff auf ein lokales Datei-Handle.

Um dieses Problem zu vermeiden, habe ich mir zwei Maßnahmen überlegt:
-ReadFile bekommt einen neuen, optionalen Parameter "transfer". Wenn dieser auf True gesetzt ist, wird als Rückgabewert zusätzlich zum Handle-Objekt der gesamte Datei-Inhalt übermittelt. Auch alle Read-Befehle (ReadLine, ReadString, ReadInt etc.) bekommen einen optionalen Parameter "noHttpRequest", welcher bei True dazu führt, dass kein Server-Request durchgeführt wird, sondern die bereits übermittelten Daten direkt ausgelesen werden.
-WriteFile bekommt einen neuen, optionalen Parameter "writeLater". Wenn dieser auf True gesetzt ist, wird nur ein lokales Datei-Objekt erzeugt, aber noch nicht an den Webserver gesendet. Jetzt können verschiedene Write-Befehle (z. B. WriteLine, WriteString, WriteInt etc.) nacheinander angewendet werden, bei denen man den Parameter "noHttpRequest" auf True setzen muss, um zu verhindern, dass die Änderungen an den Server übermittelt werden. Erst wenn man CloseFile aufruft, wird die gesamte Datei an den Server geschickt und dort erstellt bzw. überschrieben.

Diese Lösung scheint mir am elegantesten, weil der Nutzer auswählen kann, ob er (wie in BB) zeilenweise die Dateiinhalte abfragt bzw. schreibt, oder in einem Stück. Warum implementiere ich nicht konsequent eine von beiden Varianten? Weil es für beide Varianten Anwendungsfälle gibt, die sie rechtfertigen:
- Eine große SaveGame-Datei soll als ein Datenpaket geschrieben bzw. empfangen werden, weil man alle darin enthaltenen Informationen benötigt.
- Eine Settings-Datei mit tausenden Einträgen enthält vielleicht irgendwo in Zeile 4.183 einen Wert, den der Benutzer jetzt abfragen bzw. ändern will. Warum sollte er dazu die gesamte Datei auslesen bzw. überschreiben und unnötigen Traffic erzeugen?

BlitzBasic ist tot - es lebe BlitzBasicScript

Sonntag, 30. Dezember 2018 von Spark Fountain
user posted image

Ungefähr 1. Quartal 2004
Während einer Projektwoche in meiner alten Schule wollten ein paar Freunde und ich ein Computerspiel entwickeln, ohne dass wir vorher jemals programmiert hatten. Durch Internet-Recherche haben wir Blitz3D entdeckt und angefangen, die ersten kleinen Grafikdemos zu produzieren. Somit war der Grundstein für mein Interesse an der Spieleprogrammierung gelegt.

27. Februar 2008
Meine Eltern schenkten mir zum Geburtstag eine Blitz3D-Lizenz, die damals noch ungefähr 100€ kostete. Seitdem konnte ich an BCC's teilnehmen, weil es dort üblich war, eine EXE-Datei mit abzugeben, was vorher mit der Demoversion nicht möglich war . Very Happy

Donnerstag, 08. Juni 2017
Wie die BlitzForum-Community diesem Post entnehmen konnte, hat Mark Sibly (der Entwickler der Blitz-Sprachen) sich entschieden, seine Webseite zu schließen und damit quasi offiziell jeden weiteren Support einzustellen. Die Blitz-Sprachen sind mittlerweile alle Open Source verfügbar und in diversen Threads wurde (und wird?) diskutiert, ob es sich heutzutage überhaupt noch lohnt, etwas in Blitz3D oder BlitzMax zu programmieren.

Anfang März 2018
Da ich von Berufs wegen täglich mit Webentwicklung zu tun habe, kam mir der Gedanke, ob es nicht möglich wäre, eine Neu-Implementierung von Blitz3D mithilfe von Webtechnologien zu wagen. Folgende Aspekte haben mich dazu bewogen:

- moderne Browser bieten mit Canvas und WebGL die Möglichkeit, anspruchsvolle 3D-Berechnungen auf der Client-GPU (bzw. CPU) durchzuführen
- Spiele werden (theoretisch) auf jedem Gerät spielbar, das einen Webbrowser besitzt und sind damit plattformunabhängig
- die imperative Syntax von BlitzBasic lässt sich relativ leicht mit JavaScript imitieren
- moderne Features wie Schattenberechnung oder Physik, welche in modernen WebGL-Frameworks bereits enthalten sind, könnten in BlitzBasicScript übernommen werden, z. B. "CreateShadowMap"

Mir war allerdings auch klar, dass es einige Probleme geben würde, wenn man den klassischen Blitz3D-Befehlssatz in den Browser übertragen will:

- Datenströme: Da der Browser eine Sandbox ist, kann nicht einfach lesend und schon gar nicht schreibend auf lokale Dateisysteme zugegriffen werden
- spezielle Befehle wie CallDLL oder Exec lassen sich nicht nachbilden, weil dabei nativer C++ Code ausgeführt werden müsste
- Blitz3D arbeitet komplett synchron, während JavaScript-Funktionen zum Laden von Ressourcen asynchron arbeiten

Nach vielen Versuchen und einer ersten Implementierung in purem JavaScript habe ich mich ungefähr im September entschieden, das ganze noch einmal sauberer und effizienter mithilfe von Angular und TypeScript anzugehen. Der Technologie-Stack sieht in etwa so aus:

- Framework für 3D: BabylonJS
- Framework für die Webanwendung: Angular 7 mit TypeScript
- Framework für die GUI: natives HTML

Und so wird aus einem Stück BlitzBasic-Code ein lauffähiges BlitzBasicScript-Programm:

- der Code wird vom Lexer in Tokens zerlegt
- die Tokens werden vom Parser analysiert und in einen abstrakten Syntaxbaum überführt
- der Codegenerator (welcher ausführbaren TypeScript- bzw. JavaScript-Code generiert) erzeugt aus der abstrakten Syntax konkrete Methoden-Aufrufe, welche von speziellen Angular-Services zur Laufzeit ausgeführt werden (z. B. assign für Zuweisungen, evaluateExpression zum Auswerten von Ausdrücken etc.)
- der generierte Zielcode wird "Zeile für Zeile" abgearbeitet und wartet dank Observables auch im Falle von asynchronen Lade-Operationen so lange, bis das vorhergehende Statement komplett abgearbeitet wurde

30. Dezember 2018
Nach neun Monaten Entwicklungszeit möchte ich ein erstes Lebenszeichen dieses Projekts senden, auch wenn es gerade noch nicht so viel zu sehen gibt und die Website nur aus einem Platzhalter besteht. Dafür lässt sich einiges Positives berichten:

- 2D- und 3D-Grafik lassen sich kombiniert darstellen mithilfe von zwei übereinander liegenden Canvas-Elementen
- mithilfe von Observables können asynchrone Prozesse (z. B. das Laden von Ressourcen) synchron nacheinander abgearbeitet werden, sodass der ursprüngliche Blitz3D-Codeflow erhalten bleibt und keine undefinierten Zustände eintreten
- der Lexer ist bereits fertig
- der Parser steht in seinen Grundzügen
- der Codegenerator wurde noch nicht (brauchbar) implementiert, aber es existieren schon Design-Entwürfe
- die BlitzBasic-Funktionen wurden nahezu vollständig strukturiert und ungefährt die ersten 20 prototypisch neu implementiert
- das Gesamtpaket ist als Angular-Library auf GitHub verfügbar (natürlich Open Source)

Ausblick
Weitere Infos und Bilder folgen ab jetzt regelmäßig. Ich hoffe, dass ein bisschen Interesse an BlitzBasicScript innerhalb der Community aufkommt und wir alle dadurch mittelfristig die Möglichkeit bekommen, einen Teil unserer alten Codes in einer neuen, modernen Umgebung wieder aufleben zu lassen.

Gehe zu Seite Zurück  1, 2