Registriert: Do Sep 02, 2004 19:42 Beiträge: 4158
Programmiersprache: FreePascal, C++
Ich stell auch mal wieder was vor… Und zwar, mal wieder, einen Boulder Dash clone. Ich hatte den vor einiger Zeit schonmal in Pascal angefangen und dann wegen numerischer Probleme (wenn sich jetzt wer fragt, wie man bei Boulder Dash numerische Probleme bekommen kann, der darf gespannt weiter lesen: dazu werde ich ein paar Worte verlieren ) liegen lassen. Unten gibts auch schon Videos!
Spielidee Prinzipiell orientiert sich ManiacLab an Boulder Dash – bloß mit mehr Lasern, Rauch und Explosionen! Wer schonmal Safrosoft RoX gespielt hat (wo es ja immer hin explosives und minen gibt): ManiacLab wird härter.
Aber erstmal zu Boulder Dash, dem Vorbild: Hierbei geht es darum, sich durch ein Level, welches in gleichgroße Quadrate (Zellen) unterteilt ist zu buddeln, welches mit Wänden, wegräumbarer Materie und Steinen durchsetzt ist. Man bewegt sich mit seinem Spielcharakter durch dieses Level. Neben diesen Objekten finden sich auch noch irgendwelche „Zielsteine“ die man aufsammeln soll (bei RoX waren das unids, bei anderen sind es Goldstücke, you get the idea). In jedem Quadrat befindet sich genau ein Objekt oder eben nichts. Außerdem gibt es bei einigen Spielvarianten (eventuell auch beim original) auch noch Gegner, die einen jagen und von denen man bei Kollision getötet wird. Ebenso gibt es Türen, also Felder, die man nur durchqueren kann wenn man einen passenden Schlüssel hat und so weiter.
ManiacLab wird dem nicht nur explosive Teile hinzufügen. Auch geplant sind Laser, die den Charakter erhitzen („wtf, erhitzen?!“ – siehe weiter unten zur Physik) und recht schnell töten. Ansonsten das übliche: Explosive Steine, die triggern wenn Dinge auf sie drauf fallen oder sie auf Dinge drauf fallen oder in eine Explosion geraten oder sich überhitzen.
Physik Man ahnt es schon: Die Physik muss „etwas“ abgefahrener sein als beim Original. Das ist sie auch: Jede Objektzelle ist in (mindestens; mal sehen wie das im finalen Release wird) 4×4 Physikzellen unterteilt, die jeweils Eigenschaften wie Luftdruck, Temperatur und Nebel/Rauchdichte haben. Dazu noch einige Ableitungen dessen und andere Informationen. Damit ist es sogar möglich, (angenäherte) Wellensimulationen zu berechnen (siehe Videos).
Der Plan und Sinn ist es, das Spielprinzip mit ein paar Physikalischen Effekten aufzuwerten. Beispielsweise könnte man sich Level vorstellen, in dem es gilt, eine „Klimaanlage“ durch Schalten von Luftdurchlässen so zu regulieren, dass das Sprengstofflager nicht explodiert. Gleichzeitig muss man darauf achten, dass man sich nicht selber die Luft abgräbt. Die Laser sind außerdem nur im Rauch sichtbar. Wenn man also durch eine verlaserte Umgebung läuft, muss man irgendwie Rauch erzeugen bzw. in Richtung der Laser treiben.
Um solche komplexen Szenarien zu ermöglichen, soll es einfache Ventilator-Elemente, Rauch-Emitter, Luft-Absorber/Emitter und ähnliches geben, die entweder die ganze Zeit laufen oder durch den Spieler an und aus geschaltet werden können.
Interaktion mit der Physik Neben den genannten indirekten Interaktionen mit der Physik gibt es auch noch einige aktive Elemente. Der Charakter kann z. B. von einem Laser gebraten, von Hochdruck zermatscht oder einfach auf kleiner Flamme geröstet werden. Direkt neben einem Explosive stehen wenn es hochgeht ist sicherlich auch keine gute Idee. Wenn der Druckgradient auf zwei seiten des Charakters zu groß wird, wird er auch zwangsbewegt (das gilt für alle Objekte im Spiel). Dies kann man z. B. nutzen, um eine Passage, die durch einen Stein blockiert ist, freizuräumen, vorrausgesetzt, man hat Zugang zu einem passenden Ventilator .
Numerikhölle Wer ein wenig Ahnung von den Formeln dahinter hat, der wird schon erahnen, dass es einige Situationen gibt, in denen einem die Numerik einen streich spielen kann. Es ist mir auch während des Testens mehrfach passiert, dass mir die Werte dann richtung +∞ oder -∞ abgeraucht sind . Inzwischen ist das durch angemessene Dämpfung (Massenträgheit) und Reibung aber ziemlich gut eingedämmt (nicht mit Stroh).
In der Pascal-Version des Projektes hatte ich damals das Problem, dass mir aufgrund numerischer Probleme (Division durch kleine rundungsfehlerbehaftete Zahlen) die Thermodynamik um die Ohren geflogen ist und sich das ganze Level unter bestimmten Bedingungen sehr schnell aufgeheizt hat, was nicht erwünscht ist .
Systemvorraussetzungen (Performance) Jetzt kommen wir zum unerfreulichen Teil. In einem Level mit 144×144 Spielzellen haben wir logischerweise 576×576 Physikzellen, das sind also 331776 Zellen (von denen die meisten vier Nachbarn haben), die jeden Frame berechnet werden müssen. Da die Physik hochgradig anfällig für numerische Fehler ist und außerdem auf einer tatsächlichen Integration basiert, kann man die Länge des Zeitfensters, das zu einem Frame gehört, nicht variieren. Statt dessen muss halt für alle 10 ms ein Physikframe berechnet werden (die Simulation genügt meinen Ansprüchen dann, wenn sie auf ca. 100 Hz läuft).
Das bedeutet, dass zwischen Nachbarzellen der Luftfluss bestimmt und angewandt werden muss. Dabei muss Wärmeenergie korrekt umgerechnet übertragen werden, Nebel wird auch mitgeschleift. Sowohl Wärme als auch Nebel unterliegen zeitgleich der Diffusion, erfahren also eine zufällige Ausbreitung im Raum.
Daher habe ich recht bald angefangen, das ganze zu parallelisieren (denn auf einem Kern reicht die Performance ohne Optimierung nicht aus). Es werden so viele Threads gestartet, wie CPU-Kerne vorhanden sind. Jeder Thread bekommt einen Streifen vom Level. An den Rändern wird dank Double-Buffering nur einmal kurz Synchronisation gebraucht, dann können die Threads lustig drauf los rechnen. Auf meinem 3.3 GHz Sechskerner erzeuge ich mit einem 100×100 Tile (also 400×400 Zellen) Level eine CPU-Last von 50% (umgerechnet drei Kerne unter Volllast), ohne Compileroptimierungen (≈30% mit -O3, also ca. 2 Kerne auf Volllast). Das ist okay so – ich habe mit absicht so entwickelt, dass der Code lesbar bleibt und die Optimierung dem Compiler überlassen (der das auch ganz gut zu machen scheint).
Technologie & Libraries Die Physiksimulation basiert auf dem CA-Paper von Tom Forsyth. Das Projekt wird in C++11 (und später mit Python als Skriptsprache) entwickelt.
Media / Videos Hier eine Auswahl von Videos. Wer mag, kann direkt im Directory Listing aufm Webserver nachschauen, um eine aktuelle und vollständige Liste zu erhalten.
CA-Draft, Basic (damals noch mit Terminal-Ausgabe, nur ein einfacher emitter der sich im Kreis bewegt, oben Flussrichtung, unten Druck)
CA-Draft, Wellen (ebenfalls Terminal, diesmal Wellen mit Interferenzerscheinungen)
_________________ If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung. current projects: ManiacLab; aioxmpp zombofant network • my photostream „Writing code is like writing poetry“ - source unknown
„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb
Registriert: Do Sep 02, 2004 19:42 Beiträge: 4158
Programmiersprache: FreePascal, C++
Gleich auch mal nen neues Video produziert. Da die Stamp-Funktion (also die Funktion zum blockweisen Setzen von Physik-Blockaden (die roten Blöcke im Video)) recht neu ist, musste ich noch ein wenig rumspielen. Das Ergebnis gibts hier als Video.
Man sieht vier Objekte, die dicht hintereinander im Kreis bewegt werden. Das wird im Spiel natürlich nicht möglich sein, ist aber ein guter Test für die Robustheit der Physikengine (in früheren Versionen wäre mir das gnadenlos abgeraucht, weil ein Druckwert von 0 vorkommt ). Auch schön erkennen kann man den Effekt des Windschattens: Die Objekte ziehen ein Niederdruckgebiet (schwarz) hinter sich her. Was hier aber ganz klar noch fehlt ist die Übertragung von Impuls (also Geschwindigkeit) auf die Luft, wenn sich das Objekt bewegt. Da werde ich mal sehen müssen, wie ich das am schlausten umsetze, da man dazu den Rand des Objektes kennen muss. Wahrscheinlich werde ich den „Stempel“-Datentyp etwas erweitern müssen, und ebenso die Funktion zum Setzen des Stempels.
Auf meinem System ruckelt das ganze übrigens nicht… Keine Ahnung warum das bei der Aufnahme ruckelt, gtk-recordMyDesktop ist schuld .
grüße
_________________ If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung. current projects: ManiacLab; aioxmpp zombofant network • my photostream „Writing code is like writing poetry“ - source unknown
„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb
Registriert: Do Sep 02, 2004 19:42 Beiträge: 4158
Programmiersprache: FreePascal, C++
Und wieder gibt es ein neues Video (und einen Screenshot). Dort kann man die erste einfache Physik sehen, nämlich das Spielobjekte nach unten gezogen werden, sofern sie von der Gravitation beeinflusst sind. Dabei inteagieren sie natürlich weiter mit der Thermodynamik-Simulation⁽¹⁾, was schöne Wellenfronten erzeugt. Vorallem wenn sie dann alle am unteren Spielfeldrand aufprallen, kann man sehr schöne Reflektionsmuster erkennen. Einfach mal reinschauen. Das Video ist zwar diesmal bestialisch groß (32 MB), geht aber auch 1:26. Für diejenigen, denen das zu groß ist, habe ich einen einzelnen Screenshot.
Viel spaß beim Draufschauen!
⁽¹⁾: Ich bezeichne die feine Zellensimulation jetzt einfach mal als Thermodynamik-Simulation; Vom physikalischen Standpunkt her ist das auch relativ korrekt, selbst wenn ich keine Temperatur dabei habe; Thermodynamik beinhaltet auch Druck und Volumen von Gasen
_________________ If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung. current projects: ManiacLab; aioxmpp zombofant network • my photostream „Writing code is like writing poetry“ - source unknown
„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb
Registriert: Do Sep 02, 2004 19:42 Beiträge: 4158
Programmiersprache: FreePascal, C++
Soo, jetzt auch echte Thermodynamik. Wärmeleitung ist soweit implementiert. Um das ganze ordentlich umzusetzen, musste ich das Vorgehen aus dem Paper etwas abwanden. Im Paper wird die Temperatur selber gespeichert. Das ist ein Problem, denn wenn man den Temperaturfluss durch Luftströmungen betrachtet, kann man das nicht trivial auf eine Addition bzw. Subtraktion zu dem vorherigen Wert runterbrechen. Man hat da eine Formel die ungefähr so aussieht:
T' = (p*T+p_f*T_f)/(p+p_f)
wobei T und T' die alte bzw. neue Temperatur der Zelle, T_f die Temperatur der dazugeflossenen Luft, p die alte Luftmenge der Zelle und p_f die dazugekommene Luftmenge sind. Das gibt aber Probleme, wenn die gleiche Zelle mehrfach nacheinander bearbeitet wird (was selbst ohne Threading passiert). Nun verwende ich ja einen doppelten Puffer, d.h. die alten Werte der Zelle werden immer aus dem Backpuffer geholt und das Ergebnis wird in den Frontbuffer geschrieben. Was ist nun aber, wenn sich die Temperatur ändert, weil zwei Luftströmungen, eine von oben und eine von rechts, unterschiedlich temperierte Luft hinzuführen? Das kann man so kaum abbilden, wenn man das nicht von vornherein einkalkuliert. Dann wird die Methodik aber nicht mehr so schön lokal, also bin ich einen anderen Weg gegangen.
Ich speichere in den Zellen jetzt nicht die Temperatur, sondern die Wärmeenergie. Der Unterschied ist nur marginal. Die Temperatur ergibt sich aus Wärmeenergie durch Temperaturkoeffizient. Für die Simulation treffe ich die (für hinreichend kleine Zellen vertretbare und vor allem zum obigen Modell äquivalente) Annahme, dass die Wärmeenergie in einer Luftzelle gleichmäßig auf alle Luftmoleküle verteilt ist. Dann ergibt sich die Temperatur in einer Luftzelle aus Wärmeenergie durch Konstante mal Luftdruck. Der Einfachheit halber setze ich diese Konstante einfach eins, aber das ist egal.
Damit wird der Temperaturfluss durch Luftströmung aber denkbar einfach und lässt sich auf eine solche Form bringen:
U' = U + U_f, mit U_f=U_s/p_s * p_f
Dabei sind U bzw U' die alte und neue Wärmeenergie der Zelle, U_f die Wärmeenergie die mit dem Luftfluss kommt, U_s und p_s sind Wärmeenergie und Druck der Zelle aus der der Luftfluss kommt und p_f ist die Flussmenge. Wenn man alle Frontbufferzellen mit den Werten aus dem Backbuffer initialisiert, wird das im Code dann ein f_cellA->heatEnergy += …, was auch bei mehrfacher Ausführung noch korrekte Ergebnisse liefert .
Natürlich gibts dazu auch wieder ein Video (2.4 MiB). Diesmal ist nicht der Luftdruck sondern die Temperatur aufgetragen. Rot ist wie immer ein Objekt. Neben den (heißen) herabfallenden Objekten findet sich hier auch eine statische Wärmequelle. Besonders Aufmerksamkeit möchte ich auf die Verwirbelungen in der Temperatur richten, die Auftreten, wenn ein Objekt dicht an der Wärmequelle vorbeifällt oder generell dicht an einer Temperaturkante. Dann sieht man, wie die Druckwelle des Objektes die Temperatur verändert, weil sie Luft geringerer Temperatur mitbringt.
Als nächstes steht auf der ToDo Konvektion, dann werde ich auch nochmal was zu der Performance sagen. Die hat sich mit den zusätzlichen Berechnungen natürlich geändert.
Viel Spaß beim Schauen!
_________________ If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung. current projects: ManiacLab; aioxmpp zombofant network • my photostream „Writing code is like writing poetry“ - source unknown
„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb
Registriert: Do Sep 02, 2004 19:42 Beiträge: 4158
Programmiersprache: FreePascal, C++
Konvektion ging schneller als gedacht . In den Demovideos dazu verwende ich andere Parameter als im Spiel, um die Konvektion besonders deutlich zu machen. Der Wärmefluss durch Wärmeleitung wird später schneller sein. Das Video ist im Split-Screen. Auf der linken Seite gibts den Luftdruck, auf der rechten Seite die Temperatur zu sehen. Die Wärmequelle oszilliert nun zwischen Quelle und Absorber hin und her und ist mittig platziert, damit man das geschehen um die Quelle herum besser in beiden Räumen verfolgen kann.
Nun ein paar Worte zur Performance. Die Physik ist ja nun soweit fertig (bis auf Rauch/Nebel, der wie eine vereinfachte Temperatur behandelt wird), der Rest ist mehr Spiellogik. Auf meinem Sechskerner hat die Simulation wie im Video gezeigt mit -O2 nun eine CPU-Last von ca. 50%, was schon arg viel ist. Das sind immerhin drei Kerne auf Volllast. Ohne Compileroptimierungen läuft die Simulation nicht immer flüssig. Vorallem das Bewegen von Objekten kostet viel Leistung, da ich viel Speicher durch die Gegend schieben muss und das vorallem nur passieren kann, während die Simulation pausiert ist (aus offensichtlichen Synchronisationsgründen).
Ich bin mir noch nicht über die (maximale) Levelgröße im finalen Release sicher, mehr als 100×100 Tiles ist aber angesichts dieser Zahlen unrealistisch. In RoX gingen 50×50 Tiles, das ist ermutigend . Mal sehen wie dann letztendlich die Performance ist, aber auf meinem System würde 50×50 durchaus eine weitere Subdivision erlauben, also 5×5 Physikzellen / Levelzelle.
grüße
_________________ If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung. current projects: ManiacLab; aioxmpp zombofant network • my photostream „Writing code is like writing poetry“ - source unknown
„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb
Registriert: Do Sep 02, 2004 19:42 Beiträge: 4158
Programmiersprache: FreePascal, C++
Schwupp. Diese Drittsemestler werden mir noch meine Prüfungen versauen.
Naja, ich musste mal wieder an ManiacLab bauen, nachdem ich denen das kurz gezeigt habe, und wir folgendes Szenario ausprobierten: Einen Turm aus Wänden, und dort drin Objekte herunterfallen lassen. Die Simulation bekommt es schon hin, dass die Objekte eine Druckwelle vor sich herschieben, wie in [wer nur wenig zeit oder bandbreite hat, schaut lieber das video unten] diesem frisch erstellten Video zu sehen (2.0M). Leider fällt einem sofort ein Bug auf: Oberhalb der Cubes bleibt kein Vakuum zurück, sondern noch Luft.
Der Grund ist, dass die Physikengine derzeit verdrängte Materie gleichmäßig (sofern Platz) um das Objekt herum verteilt. Das ging mir dann recht bald auf die Nerven und während der Thermodynamik-Vorlesung heute habe ich mir einige Gedanken gemacht, wie man das lösen kann. Der Methode, die für das verschieben eines “Stamps” („Abdruck“ den ein Objekt in der Simulation zurücklässt), bekommt jetzt einen Vektor, der die Bewegungsrichtung des Objektes angibt. Mit diesem Vektor werden die Koordinaten potentiellen Zielzellen skalarmultipliziert, sodass ich also eine Zahl herausbekomme, die mir angibt, ob sich die Zelle in Richtung der Bewegung befindet oder nicht. Nach dieser Regelung wird nun Materie verteilt, und das klappt auch gleich viel besser!
Leider musste ich dafür an anderen Stellen in der Physikengine rumschrauben, da dies erfordert, dass die Engine ordentlich mit Vakuum umgehen kann – das konnte sie bisher nicht. Ich habe Vakuum (zumindest fast-vakuum) bisher als Fehlerbedingung betrachtet und dort an einigen Stellen die Notbremse gezogen. Inzwischen funktioniert das alles nun ordentlich und man kann richtig coole Effekte beobachten, wie in diesem Video (2.5M) gezeigt. Sieht schon sehr cool aus und entspricht schon ziemlich dem, wie man es im Spiel verwenden könnte.
Da tauchen noch einige Artefakte auf. Wenn sich die Röhre unten wieder füllt bzw. die Druckwelle vom Aufprallenden Container sich nach oben durcharbeitet, entstehen einige lustige nebenartefakte, die langsam nach unten wandern. Diese halte ich aber nicht weiter für wichtig. Ich habe einen Testlauf im Hitzevisualisierungsmodus gemacht und es treten keine kritischen Probleme auf .
grüße
_________________ If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung. current projects: ManiacLab; aioxmpp zombofant network • my photostream „Writing code is like writing poetry“ - source unknown
„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb
Registriert: Do Sep 02, 2004 19:42 Beiträge: 4158
Programmiersprache: FreePascal, C++
Und wieder ein paar Fixes. Insbesondere ist mir beim Rumspielen eine Asymmetrie der Simulation aufgefallen. Mehrere Megabytes Debugausgaben und zwei Klausuren später hab ich dann endlich die Ursache gefunden. Der diff ist erschreckend kompakt:
@@ -597,7 +597,7 @@ inline double AutomatonThread::flow(const Cell *b_cellA, Cel
tcA / 4.
);
- f_cellA->flow[direction] = flow;
+ f_cellA->flow[direction] = applicableFlow;
f_cellA->airPressure -= applicableFlow;
f_cellB->airPressure += applicableFlow;
So kanns gehen . Vorher habe ich noch die Rauchsimulation fertig gebaut, hiervon gibts natürlich nen Video. Dies ist eigentlich der showcase der kompletten Physiksimulation, wie sie derzeit existiert. Das Video ist wieder Splitscreen, diesmal auf der linken Seite wie gewohnt den (Luft-)Druck und auf der rechten Seite die Rauch-/Nebeldichte. Wieder sei erwähnt, dass es sich nicht um zweimal die gleiche Szene handelt sondern ich einfach nur in der Mitte den Visualisierungsmodus teile.
Bevor die Objekte eingefügt werden, sieht man sehr schön den Effekt der Konvektion. Die Wandteile kommen mit einer hohen Temperatur in die Szene, was die Luft drumherum aufheizt und insbesondere innerhalb des Schlauchs schnell zu einem Druckgefälle führt. Dann kommen die Objekte hinzu, welche natürlich den üblichen Luftverdrängungsfoo betreiben. Hier sieht man im Vergleich zu den vorherigen Videos einige Tweaks an den Simulationsparametern. Unter anderem wurde die zugrundeliegende Simulation auf 200 Hz getuned, während die Objektgeschwindigkeiten weiter gleich blieben. Das behebt den krassen Überschallkegel, den die Objekte hinter sich herzogen (selbst wenn er cool aussah).
Dann spawne ich etwas Rauch, der auch spontan anfängt, rumzudiffundieren. Die Objekte die da hindurchfallen beinflussen den natürlich auch – direkt durch Verdrängung und indirekt dadurch, dass die Luftdruckwellen natürlich auch Rauch abtransportieren.
Viel spaß beim schauen. Als nächstes auf dem Plan steht wieder auf Klausuren vorbereiten, danach gehts an etwas Content. Ich hatte überlegt, als nächstes mal etwas rudimentäres Gameplay und Rendering einzubauen, sodass ich vielleicht einen Editor bootstrappen kann. Hey, ich darf mir ein Levelformat ausdenken! Das hab ich schon seit Jahren nicht mehr gemacht .
grüße, Horazont
_________________ If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung. current projects: ManiacLab; aioxmpp zombofant network • my photostream „Writing code is like writing poetry“ - source unknown
„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb
Registriert: Do Sep 02, 2004 19:42 Beiträge: 4158
Programmiersprache: FreePascal, C++
Dateiformat
Vorweg: Wer ein Beispiel vom binären Aufbau sehen möchte, der schaue mal hier: tests/decode_container.cpp.
Wie in meinem „EBML… oder was?“-Thread diskutiert, war ich ja auf der Suche nach einem einfachen Dateiformat / Framework für Binärdateien. Ich möchte zunächst meine Entscheidung und dann das Ergebnis hier diskutieren.
EBML hatte ich schonmal von Frase gehört und das klang ganz fancy. Daher hab ich halt hier nachgefragt und wurde außerdem auf BSON gepointed. BSON ist aber recht schnell rausgeflogen – das Format sieht keine unsigned integer vor. Das äh… ist schlecht .
EBML wird von den Matroska-Leuten entwickelt und verwendet und es existieren schon durchaus einige Libs dafür. Leider fand ich die Referenzimplementation in C++ ziemlich undokumentiert vor – noch dazu betreiben die da irgendwelche Präprozessor-Magic, um die DTDs zu definieren. Also habe ich erstmal angefangen, einen eigenen EBML-Paresr zu schreiben, habe das aber dann recht bald wieder abgebrochen. Der Grund ist, dass EBML recht gräusig zu Parsen ist, wenn man alle Sonderfälle in der Spec betrachtet. Und wenn ich mich nicht an die Spec halte kann ich auch gleich nen eigenes Format bauen.
Also habe ich mich jetzt eben dafür entschieden. Das Format ist an sich erstmal relativ simpel aufgebaut und unterstützt von Haus aus die grundlegenden C-Datentypen: (u)int32, (u)int64, bool, float32, float64, utf8-string und blob. UTF8 String ist ein implizit mit #0 beendeter C-String (die Null wird nicht mit in die Ausgabe geschrieben), der als UTF8 interpretiert wird und Blob ist einfach nur ein Block Binärdaten. Beide schreiben auch ihre Länge mit in die Ausgabe, die anderen Datentypen sind fixed-size.
Um das Format kompakt zu halten, verwende ich bei den Metadaten durchgängig varints, die nach der EBML-Spec funktionieren. Beim ersten Byte des Ints gibt die Anzahl der führenden binären 0en (von most-significant abwärts) an, wieviele weitere Bytes folgen. Nach der ersten 1 im ersten Byte beginnen dann die tatsächlichen Daten des Integers. Damit kann man fast einen vollständigen Int64 serialisieren. Für die meisten Fälle braucht man aber nur niedrige Werte, womit man hier bei großen Dateien unheimlich viel Platz und auch I/O spart (auf kosten von etwas Berechnung).
Ebenso gibt es Container, die andere Records (auch wiederum Container) enthalten können. Die Container haben erstmal einen varint, der die Flags des containers angibt, die niedrigsten vier Bits davon sind bereits reserviert: Eins um anzugeben, dass der Container die Anzahl seiner Children angibt, einer um anzugeben, dass der Container von einem END_OF_CHILDREN-Marker beendet wird⁽¹⁾ und einer um anzugeben, dass nach der Payload der Hash der enthaltenen Daten steht. Die Art der Hashfunktion steht nach der (optionalen) Länge des Containers als varint. Derzeit unterstütze ich keine Hashfunktionen .
Jedes Element wird durch eine ID identifiziert, die analog zum XML-Tag-Namen gesehen werden kann. Über diese ID kann sich die Anwendung dann die Daten rausschnorcheln. Bisher sind eine Reader und eine Writer-Klasse implementiert, die beide noch kein Streaming erlauben. Der Reader bringt aber schon die grundlegenden Methoden dafür mit: Man kann an ein Event binden und dort eine Node verarbeiten und verhindern, dass diese Node in den endgültigen Baum eingehangen wird. Damit kann man z.B. große Datenmengen laden, ohne, dass diese vollständig zur gleichen Zeit doppelt im RAM liegen müssen.
Generell implementieren die Datenklassen alle eine Kopier-Semantik. Wenn man also eine UTF8-Node hat und der einen Wert zuweist, wird der String komplett kopiert. Das ist möglicherweise ineffizient – aber für sowas will ich dann irgendwann™ mal ein Streaming-Interface bauen, sodass man auch beim Schreiben nicht den kompletten DOM konstruieren muss.
Ich werde hier denke ich nur noch am Rande über das Format schreiben, welches übrigens structstream heißt . Den Code und die Entwicklung kann man aber ab sofort auf github verfolgen.
Oh, natürlich sind die ganzen I/O-Funktionen ziemlich gut durchgetestet . Dazu habe ich das C++-Unittest-Framework Catch verwendet, welches mir bei der suche nach einem selbigen über den Weg gerollt ist und auch ganz gut gefällt (siehe hier für eine kleine Einführung).
viele Grüße, Horazont
(1): Dem aufmerksamen Leser wird nicht entgangen sein, dass der E-O-C-Marker und die Längenangabe gleichzeitig auftreten können. Das ist gewollt. Das ermöglicht es dem Parser, doppelt zu verifizieren, ob alle Children auch gefunden wurden und erlaubt es so, bestimmte Arten von Beschädigungen zu erkennen.
ps.: Dokumentation für die öffentlichen Klassen dazugeschrieben
_________________ If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung. current projects: ManiacLab; aioxmpp zombofant network • my photostream „Writing code is like writing poetry“ - source unknown
„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb
Registriert: Do Sep 02, 2004 19:42 Beiträge: 4158
Programmiersprache: FreePascal, C++
Nachdem ich mehr Zeit als ich eigentlich geplant hatte bei Structstream zugebracht habe (das macht aber auch einen heidenspaß dieses Format und die Lib dazu zu entwickeln), habe ich mich nun wieder ManiacLab gewidmet.
Hier möchte ich erstmal einen Editor auf die Beine stellen. Dazu brauche ich eine funktionierende GUI und genau an diesem Teil von PyEngine arbeite ich nun. Die GUI PyEngine, um mal kurz einen überblick zu geben, verwendet einen mittels pyLR1 erzeugten CSS-Parser (ja, CSS wie in Cascading Style Sheets), um Themes zu implementieren.
Dabei wird natürlich nur ein Subset von CSS unterstützt, aber auch einige Erweiterungen die aus XUL und aufkommenden CSS Standards bekannt sind (flex boxing). Ziemlich gut unterstützte properties sind margin, padding, flex, text-align, vertical-align (etwas anders als im original, tut hier aber eher was man erwartet ), font-size, font-weight, font-family, border-radius, color, background(-color) und teile von border. Wir unterstützen keine Einheiten, sondern nehmen alle Angaben (sofern Einheitenbehaftet) als in Pixel an.
Das ganze funktioniert recht gut und wird auch durch ein paar Tests zumindest ansatzweise vor Regressionen geschützt. Das größere Problem ist es nun, die Widgets ans laufen zu bekommen und das Zeichen performant zu halten. Dies geschieht nämlich mittels Cairo und Pango⁽¹⁾ in einen Puffer, welcher dann auf die Grafikkarte übertragen wird. Es wird nur der Teil neu gezeichnet, der sich auch geändert hat (was noch fehlt, ist, auch nur diesen Teil auf die Grafikkarte zu übertragen). Das habe ich mal visualisiert: Video: Selective Repaint, 747KB, 13 Sekunden. Jedes mal, wenn die GUI neu gezeichnet wird, wird erstmal der komplette Puffer mit einem 90% transparenten Rot überflutet. Nur der neu gezeichnete Teil ist dann noch klar zu sehen. Man kann schön sehen, wie über mehrere Zeichenaufrufe hinweg der Hintergrund degradiert wird und nur die sich verändernden Buttons neu gezeichnet werden.
Die GUI ist abgesehen von der Cairo und Pango abhängigkeit vollständig in Python implementiert. Dafür performt sie noch ziemlich gut, muss ich sagen . Im Idle haben wir eine CPU-Last von ca 30% (ein Kern), was für ein Fullscreenquad und Transfer dessen auf die Grafikkarte nicht schlecht ist.
Eine kurze Demo, wie sich die GUI derzeit verhält, habe ich auch gedreht: Video: A main menu, 6.5MB, 28 Sekunden. Zu sehen ist das mögliche zukünftige ManiacLab Hauptmenü sowie im hintergrund der verantwortliche CSS-Code . Außerdem gibts diesmal auch einen statischen Screenshot.
grüße und viel spaß beim schauen
(1): Warum Pango? Man will Text-Rendering echt nicht selber machen. Spätestens bei Arabisch macht sowas keinen Spaß mehr. Pango bietet außerdem: Word wrapping, Ellipsization, Iteration über die einzelnen visuelle(!) Buchstaben (was für eine editor-komponente hilfreich sein wird) und noch einiges mehr.
ps.: Hab mal nachgemessen: Die meiste Zeit verbringt ManiacLab tatsächlich damit, die Cairo-Surface in die Grafikkarte zu kippen. Das ist natürlich unschön, mal sehen ob's schneller wird, wenn ich nur die aktualisierten Teile neu übertrage.
_________________ If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung. current projects: ManiacLab; aioxmpp zombofant network • my photostream „Writing code is like writing poetry“ - source unknown
„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb
Registriert: Do Sep 02, 2004 19:42 Beiträge: 4158
Programmiersprache: FreePascal, C++
Meh. Wie man vielleicht aus meiner Abwesenheit im IRC und im Forum raten kann, die Uni hat mich wieder. Die Bachelorarbeit verbraucht natürlich eine Menge Zeit, daher will ich hier nur den Stand reporten, den ich vor dem Beginn der Arbeit noch erreicht habe.
Ich habe natürlich die meiste Zeit an der UI geschraubt, um dem Ziel, einen Tileset- und Map-Editor zu bauen, näher zu kommen. Das ging soweit alles auch recht schnell vorran, bis ich dann den Dateiauswahldialog gebaut habe. Die UI bricht trotz clipping und culling bei 200 Listeneinträgen schon zusammen. Das ist… äh schlecht. Insbesondere weil das Profiling gezeigt hat, dass man da nicht so einfach wieder rauskommt. Der Code um ausrichten der Listenelemente ist einfach zu langsam, und das ist leider der Teil, den man nicht loswird. Daher musste ich wohl oder übel anfangen, die UI nach C++ zu portieren… Sehr unangenehm, weil eine UI mit Duck Typing viel mehr spaß macht
Naja, das ist natürlich nen Einbruch in der Motivation, weil ich praktisch nur den Code abschreibe und sonst nix mache. Außerdem löse ich noch Probleme, die ich in Python nicht hatte, was das ganze auch nicht gerade spanneder macht. So geht es langsam (sehr, insbesondere wegen Bachelorarbeit) voran und ich melde mich irgendwann dann wieder .
grüße
_________________ If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung. current projects: ManiacLab; aioxmpp zombofant network • my photostream „Writing code is like writing poetry“ - source unknown
„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb
Registriert: Do Sep 02, 2004 19:42 Beiträge: 4158
Programmiersprache: FreePascal, C++
Code Code Code. Bachelorarbeit ist abgegeben, ich baue wieder an ManiacLab bzw. an PyEngine. Dazu portiere ich weiterhin viel Python-Code nach C++. Das dauert, und man sieht nicht viel dabei, aber ich dachte, ich meld mich mal und sage, dass das Projekt noch lebt
Übrigens lebt es nicht nur, es wird sogar besser: Bei der gelegenheit nutze ich gleich das bei structstream entdeckte unittesting framework Catch um unittests in PyEngine einzubauen. Dabei habe ich gleich ein paar witzige Bugs in der Mathelib gefunden und solche Dinge. Also es geht vorwärts, langsam aber stetig .
grüße
_________________ If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung. current projects: ManiacLab; aioxmpp zombofant network • my photostream „Writing code is like writing poetry“ - source unknown
„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb
Registriert: Do Sep 02, 2004 19:42 Beiträge: 4158
Programmiersprache: FreePascal, C++
Prüfungen. Suddenly Prüfungen. Ein kurzer Zwischenreport aus der heißen Phase der Prüfungszeit. Code wird derzeit nur selten geschrieben… Aber es gibt einige konzeptuelle Fortschritte.
Zum Beispiel habe ich überlegt, dass es sinnvoller ist, den Editor für ManiacLab mit GTK3 zu bauen. Während meiner Bachelorarbeit hatte ich die Gelegenheit, mich mit glade und gtk3 auseinander zu setzen. Ich denke, dass das der bessere Ansatz sein wird für eine so GUI-Lastige Anwendung wie den Editor. Die C++-UI ist deshalb noch nicht tot. Es wird im Spiel einen Debug-Modus geben, der genutzt werden kann, um Level zu testen (das wird nicht im Editor geschehen, da ich sonst den ganzen visualisierungscode duplizieren müsste), wo man auch kleinere Manipulationen am Level zur Laufzeit vornehmen kann, um nicht immer von vorne testen zu müssen. Dafür braucht man auch ein bisschen UI.
Außerdem ist mir noch ein Stapel videos aufgefallen, die ich hier noch nicht announced habe: Das lange aber kleine Video über die Menus und Editboxen (Größe: 663KB, Dauer: 0:52). Das Textrendering und -bearbeiten geschieht wie gesagt mit Pango. Dann gibt es hier Scrollbars zu sehen (Größe: 404KB, Dauer: 0:22) und im letzten Video den Window layer (Größe: 616K, Dauer: 0:10).
So gibts zumindest was zu sehen, selbst wenn es nicht den aktuellen Code widerspiegelt.
grüße
_________________ If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung. current projects: ManiacLab; aioxmpp zombofant network • my photostream „Writing code is like writing poetry“ - source unknown
„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb
Registriert: Do Sep 02, 2004 19:42 Beiträge: 4158
Programmiersprache: FreePascal, C++
Soo… Nun da die Klausuren vorbei sind und ich aus Prag zurück bin (wer auf Knochen steht, sollte mal den Photostream-Link in meiner Signatur anklicken), bastle ich auch wieder an ManiacLab.
Genauer geht es gerade um den Editor. Dieser wird, wie gesagt, auf GTK3-Basis gebaut und kann schon Tilesets anlegen und deren Eigenschaften bearbeiten. Tiles hinzufügen oder bearbeiten geht noch nicht. Trotzdem mal ein paar Screenshots:
(klick für vergrößerung)
Das Tileset mit diesen Informationen ist übrigens 142 Bytes groß .
grüße, Horazont
_________________ If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung. current projects: ManiacLab; aioxmpp zombofant network • my photostream „Writing code is like writing poetry“ - source unknown
„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb
Registriert: Do Sep 02, 2004 19:42 Beiträge: 4158
Programmiersprache: FreePascal, C++
Es wurde weiter am Editor gefeilt. Folgende Highlights sind implementiert:
Undo/Redo für alle Operationen
Anlegen, Laden und Speichern von Tilesets
Anlegen und Löschen von Tiles
Grundlegendes Bearbeiten des „Stempels“ von Tiles
Hinzufügen eines Bildes für ein Tile
Es gibt ein kurzes Video, welches den Workflow veranschaulicht: Edit a Tile (Größe: 2.7 MB, Dauer: 1:08).
Der „Stempel“ eines Tiles gibt seine Interaktion mit der Physik an. Jede Tile-Zelle besteht aus 5×5 Physik-Zellen. Der Stempel enthält nun für jede dieser 25 Zellen Informationen. Eine Physik-Zelle kann entweder frei, blockiert, eine Quelle, eine Senke oder ein Flussfeld sein. Frei und Blockiert sollten klar sein (wobei eine blockierte Zelle den Temperaturkoeffizient vom Objekt übernimmt). Eine Senke oder Quelle kann entweder auf Luft oder auf Nebel operieren. Eine Senke entfernt einen gewissen Prozentteil der gegebenen Masse, eine Quelle fügt einen festen Absolutteil hinzu. Bisher kann der Editor aber nur das Setzen von Blockiert/Frei, der Rest kommt später.
Die Bilder werden importiert und unkomprimiert im BGRA-Format (8 Bit/Kanal) in der Tileset-Datei abgelegt, sodass man sie schön einfach auf die Grafikkarte pusten kann. Zu einem Release werde ich möglicherweise noch eine Kompressionsstufe über die ganze Tileset-Datei legen, damit die nicht so exorbitant groß werden.
grüße
_________________ If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung. current projects: ManiacLab; aioxmpp zombofant network • my photostream „Writing code is like writing poetry“ - source unknown
„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb
Registriert: Do Sep 02, 2004 19:42 Beiträge: 4158
Programmiersprache: FreePascal, C++
Updates!
Seit damals hat sich noch ein bisschen was getan. Wer sich das Video angeschaut hat, dem werden die “Unique Name”-Dialoge aufgefallen sein. Die nerven und daher hab ich die rausgeschmissen und auf unterschliedliche Weise ersetzt. Für Top-Level-Objekte (also Tilesets und Level Collections) dient einfach der Dateiname als unique name. Damit kann ich auch kostengünstig ein Tileset/eine Level Collection anhand ihres Namens finden, und die uniqueness wird auch gleich vom Dateisystem enforced.
Für die anderen wurde eine einfache UUID-Library implementiert. Ich wollte erst libuuid nehmen, aber die ist sehr Linux-gebunden. In der glib ist mWn noch kein UUID-Support, also hab ich’s schnell selber gebaut, ist ja nich viel. Mit dem uuid_random generator werden nun intern die UUIDs für Tiles und Levels erzeugt. Damit ist alle unique-name frickelei vorbei und der Workflow viel entspannter.
Besserer support für das laden und speichern ins VFS und ähnliches wurde auch implementiert, umbenennen von Tilesets und Level Collections geht ganz einfach und so weiter und so fort .
Glücklicherweise habe ich noch nicht released, denn ich habe außerdem die Kompatibilität im Dateiformat für Tilesets gebrochen. Es erwies sich als sehr praktisch, den Header und den Body zu trennen. Bisher waren die zusammen in einem StructStream. Das ist natürlich unpraktisch, wenn man z.B. ein listing mit allen Tilesets erzeugen will. Die können ja beliebig groß werden, wenn man da genug Bilder reinknallt. Jetzt kann man den Header problemlos und unabhängig vom Body lesen. Für Level Collections wurde das analog umgesetzt: Im header gibt es einen Index mit allen Levels, jedes Level ist ein eigener StructStream der nach dem Header folgt. Zum besseren Seeken sind die Startpositionen der Level-Streams mit in den Header geschrieben.
Ansonsten gabs hauptsächlich Bugfixes, die man so nicht direkt sieht. Da sich aber trotzdem einiges im Workflow geändert hat (hauptsächlich wurde dieser angenehmer), gibts trotzdem ein neues Video: Edit a tileset (Größe: 2.2 MiB, Dauer: 1:10).
Viel Spaß beim Schauen und guten Rutsch, Horazont
_________________ If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung. current projects: ManiacLab; aioxmpp zombofant network • my photostream „Writing code is like writing poetry“ - source unknown
„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb
Mitglieder in diesem Forum: Google [Bot] und 3 Gäste
Du darfst keine neuen Themen in diesem Forum erstellen. Du darfst keine Antworten zu Themen in diesem Forum erstellen. Du darfst deine Beiträge in diesem Forum nicht ändern. Du darfst deine Beiträge in diesem Forum nicht löschen. Du darfst keine Dateianhänge in diesem Forum erstellen.