Tagebuch: GalaxyCommand

Ihr habt euch schon immer mal gewünscht eure Finger an einem Smartphone zu verbrennen? Ihr seit es Leid, dass sich euer Smartphone am Ladekabel immer nur auflädt statt sich zur Abwechslung mal zu entladen?

Dann hab ich hier genau das richtige für euch: TMII’s Multithreading Monster: GalaxyCommand

Spiel: Weit im Anfangsstadium. Im Moment gibt es nur den im Video zu sehenden Editor.
Später sollen Stationen, Planeten, Handel und rundenbasierte Raumkämpfe ihren Weg in das Spiel finden.

Multithreading: 8 Kerne und eine GPU, angetrieben von Context Based Threading (CBT). Das CBT Projekt hatte ich vor längerer Zeit hier vorgestellt und dieses Projekt ist das erste auf dem CBT Framework basierende größere Programm.
Unten links kann man die Kern-Threads und ihre Zeiten betrachten.
Das Renderig findet auf einem dedizierten Thread statt welcher ohne Aufnahme bei ca. 60fps arbeitete.
Es ist beachtlich wie vor CBT die Matrizenberechnungen noch eines der Hauptprobleme waren nun auf die Kerne aufgeteilt praktisch nicht mehr ins Gewicht fallen.

Rendering: OpenGL ES 3.1
Die Rendering Performance ist mit 50-60 fps im Vergleich relativ langsam, jedoch auch kaum optimiert. Instanced Rendering und eine intelligentere GPU Pipeline stehen eher weiter hinten auf der Liste.
Auf dem Rendering Thread findet nur die Kommunikation mit der GPU statt. Die Daten werden auf den restlichen Kernen aufbereitet.

Künstliche Intelligenz: Hier wird eine künstliche Schwarmintelligenz eingesetzt, zur Kontrolle und Navigation aller an Bord befindlicher Crewmitglieder.
Auch diese Bibliothek ist eine Eigenentwicklung.
Sie skaliert sehr gut mit steigender Agenten Anzahl, was ihr in Zukunft sehr gut mit komplexeren Raumschiffen/Umgebungen zu sehen bekommen werdet.

2 „Gefällt mir“

Ui, noch ein Tagebuch. :wink:

Zum Spiel:

Das ist super! Mal abwarten. :+1:

Auf das Thema hatte ich schon gewartet :slight_smile:. Dachte mir, dass das kommen wird ^^.

Das Projekt klingt auf jeden Fall sehr spannend und ich finds interessant, dass du da auch eigengeschriebenen Libs für verwendest. An die Vorstellung von CBT erinnere ich mich auch noch, hatte den Thread witzigerweise vor noch gar nicht all zu langer Zeit mal wieder offen gehabt (war unten in Vorgeschlagene Themen gelistet^^).

Machst du hier wirklich alles selber oder setzt du auf sowas wie libgdx?

:smile:

Alles selber geschrieben. Von der Logik bis zur Darstellung und sogar selber gezeichnet. :sweat_smile:
(Außer die Musik und Effekte.)

Feedback zum Zeichenstil sind ausdrücklich erwünscht! Gerade die Bordsysteme sind per Hand gezeichnet, eingescannt und bearbeitet worden. Eigentlich eine Behelfslösung aber mittlerweile habe ich mich daran gewöhnt. :sweat_smile:

Würde aber niemandem dazu raten das gleiche zu tun. Alleine OpenGL ist bereits verdammt schwierig, und wenn man es gut machen will sprengt das alle Ausmaße - ich sage nur Concurrency auf der GPU.
LibGDX und Unity sind definitiv der bessere Weg.
Unity empfand ich aber zu barebones. Für viele Funktionalitäten muss man zusätzlichen zahlen.

Glaube ich dir aufs Wort. Hatte mal damit rum experimentiert (opengl + android) und schnell eingesehen, dass ich das nicht will ^^.

Aber es ist sicherlich interessant, sowas mal von Grund auf selber entwickelt zu haben. Kommt halt immer drauf an wie geduldig man ist. Aber ich glaub schon, dass man einen riesen Spaß damit haben kann. Und ich denk, da kannste sicherlich einiges interessantes zu berichten.

Und da dachtest du dir es ist weniger Aufwand alles selber zu machen :stuck_out_tongue: ? Aber ich glaube du hast noch ein altes Lizenz-model im Kopf. Beim neuen ist das anders:

Alle Engine-Funktionen zur Erstellung und Optimierung von High-End-Spielen und interaktiven Erfahrungen. Uneingeschränkte Freiheit zur Bereitstellung auf allen unterstützten Plattformen

Klar, aber es gibt keinerlei Vorlagen, auch in Unity muss man alles von Scratch programmieren, oder Vorlagen anderer kaufen. Ein Lizenzmodell hätte mir hier besser gefallen, dann kaufe ich nicht die Katze im Sack.
Meine Libs sind außerdem alle bereits in Java geschrieben und nicht auf die Unity Umgebung augerichtet. Zudem kenne ich mich mit Unity nicht aus, weshalb natürlich die Frage im Raum steht: Ist es schneller auf Unity zu wechseln, sich C# nochmal zur Gemüte zu führen als es selber zu machen? (wahrscheinlich schon)

Ja und nein. Klar bekommst du keine assets geschenkt — doch schon, aber das mein ich nicht. Was dir Unity liefert ist halt das komplette Paket drumrum. Einen Editor den du super einfach erweitern kannst und eine komplette Engine. Physik & Co verwende ich einfach und schreibe mir das nicht neu.

Nun ja - kommt drauf an wie du dich mit Unity auskennst. Leider sind alle Tutorials im Netz die ich im Netz gefunden hab kacke. Auch das Video-tutorial auf Udemy wo der Tutor eigentlich von Unity3D unterstützt wird find ich mist. Warum: Die Dinger sind drauf aus, dass du schnell den nächsten Space Invador schreibst. Aber sinnvolle Architektur bekommste da nicht mit. Meine Einarbeitungsphase war Feature Runner - und das Projekt ging bis jetzt 5 Monate (Fast ein halbes Jahr).

Du solltest dir halt klar sein, was deine Ziele sind. Wegen einem Spiel sich in Unity einzuarbeiten ist imho schwachsinn. Wenn es dir „nur um das“ Spiel geht und du dann wieder mal was mit libs machst, dann noch eine lib und vllt wieder ein Spiel oder ein anderes Programm was aufwendige Threads benötigt, dann hast du glaub mehr Spaß und Freude daran so weiter zu machen wie du gestartet hast. Bei dir geht es da ja auch schon um mehr als das Spiel. Ich kann mir gut vorstellen, dass du das Spiel nutzen kannst um deine libs weiter zu verbessern.

Ich nutze Unity weil es mir wirklich nur REIN um die Spiele geht. Ich sehe das als netten ausgleich zu meiner täglichen Arbeit wo ich nur mit Java und reiner Software Entwicklung zu tun hab. Ich will nur Code schreiben der relevant für das Spiel ist und nichts anderes. Deswegen sind für mich die 5 Monate auch keine Abschreckung - da ich mir vorstellen kann bis ich alt und grau bin nebenbei kleinere Handyspiele zu entwickeln. Einfach weils Spaß macht. Sollte dich sowas reizen, dann würde ich dir definitiv empfehlen dir irgendwann mal Unity anzuschauen :wink:

Gestern habe ich mich in philosophische OOP Fragen verloren, bei der Umsetzung eines einfachen List.add(o) :smiley:
In meinem Framework müssen benutzerdefinierte Objekte mehreren Listen hinzufügt werden. Welche das genau sind hängt stark davon ab wie das Objekt programmiert worden ist. Viele dieser Listen sind auch in Gruppen zusammengefasst, aber ein Objekt kann auch nur in einem Teil der Listen auftauchen wollen oder in mehreren gleichzeitig.

Jetzt ist nur die Frage wo ich die Logik hinpacke um genau das zu steuern.
o.addTo(ListGroup1, ListGroup2) Eigentlich weiß der Programmierer des Objektes am besten was mit jenem geschehen soll. Auf der anderen Seite müsste das Objekt dann aber die genauen Listen und Gruppen Implementation kennen.
ListGroup.add(o) Listengruppen haben keine Informationen über die spezifische Implementation des Objektes und die Logik in den Speicher zu packen erscheint mir falsch. Jedoch könnten über Visitor-Patterns und Interfaces durchaus eine Lösung gebastelt werden.
{List.add(o)} Oder an die Stelle in der das Objekt erzeugt wird, wird es auch den Listen zugewiesen.
Das könnte für teilweise starke Code Duplizierung sorgen, an jeder Stelle an der so ein Objekt erstellt wird mit ~5-6 List.add(). Dafür kennt sich Objekt und Listen aber nicht. Und der Programmierer müsste an dieser Stelle ein tieferes Verständnis über die Bedürfnisse des Objektes haben, was man über die Doku regeln kann.
Dynamische Zuweisung Zum letzten gäbe es noch die dynamische möglichst automatische Zuweisung, da es zur Laufzeit durchaus indirekte Zusammenhängigkeitsinformationen gibt. Da gibt es aber mehrere Probleme und eins davon ist "instanceof "

Wenn man dafür Regeln definieren kann, dann würde ich das glaub über einen Service lösen. Klingt nämlich nach einer Sache die ansonsten sehr schnell sehr schief gehen kann, wenn eine Liste vergessen oder falsch gewählt wurde.

Japp, nur Regeln machen heißt auch die Möglichkeiten einschränken. Bin ich ganz schlecht drin. Die Baustelle hab ich mehrmals aufgerissen, im Moment erstelle ich manuell Service-Klassen für ein oder mehrere spezifische Klassen-Container-Beziehungen. Der Übergang von funktionellem Design zu OO ist halt leider nicht so einfach, vorallem nicht in Java. Gerade hier fehlt mir die Möglichkeit dass Generics zur Laufzeit nicht mehr existieren.

Über Weihnachten habe ich noch ein bisschen gewerkelt und mich an „Instancing“ in OpenGL ES versucht und mal die App auf Android 8.0 ausprobiert. Das führt mich zu einer guten und einer schlechten Nachricht zum Ende des Jahres 2018:

  • Die schlechte Nachricht: Aus irgendeinem Grund werden die instanced Objekte nicht gezeichnet. Der Android GPU Tracer sieht auch keine Probleme. Das wird mal wieder zum Haare rausreißen wie immer in OpenGL: Nach dem Halm mit Knick im Heuhaufen suchen.

  • Die gute Nachricht: Die App läuft (Szenario oben) auf Android 8.0 auf allen Kernen und der GPU am festgelegten Limit von 500 Berechnungen/Frames pro Sekunde im Debug Mode :smiley: Das hat mich ein bisschen überwältigt, der Akkuverbrauch ist natürlich extrem aber das interessiert mich kein Stück und sehe ich eher als Kompliment!

Frohes neues Jahr euch allen!

Das Instancing funktioniert! Wie immer bei OpenGL war es nur eine winzige Kleinigkeit. In diesem Fall hab ich vergessen eine Variable zu entbinden bevor ich weitergezeichnet habe, hat nur 5 Tage gebraucht das zu finden. Ein hoch auf C.

Das ganze Instancing funktioniert jetzt nach dem Prinzip:
Mein OpenGL Wrapper fängt alle Zeichenbefehle auf und sortiert sie nach Ähnlichkeit: Welches 3D Objekt, mit welcher Textur wird mit welchem Shader und welchen Variablen gerendert. Damit bündelt er die Zeichenkommandos, packt sie in einen Buffer und schickt sie in einem Rutsch an die GPU.

Schlussendlich werden also 10.000 Crewmitglieder auf dem Raumschiff mit unterschiedlicher Position und Rotation in einem Batch gezeichnet ohne einen* Frame zu droppen!

Das ganze muss jetzt noch mit der Tiefensortierung vereinigt werden, denn ich verwende keine DepthBuffer und damit liegen die Objekte in zufälliger Reihenfolge übereinander.

*einen merklichen Performanceimpact festzustellen.

Ziemlich wuselig auf dem Raumschiff :smiley:

Die Lastenverteilung auf die einzelnen Kerne beim Laden von Spielständen funktioniert leider noch nicht richtig. Henne-Ei Problem: Um die Last gleichmäßig zu verteilen muss ich erst wissen wie viel Last ein Objekt erzeugt. Das weiß ich aber erst wenn „die Last“ ausgeführt wurde. Auf #18741 befanden sich zur Aufnahme 371 Matrizenberechnungen, auf #18748 nur eine einzige.

Heuristik etwas angepasst und das sieht doch schon sehr viel besser aus :smiley: In der Ecke unten links sieht man ganz gut die stark gleichmäßigere Verteilung.

Anmerkungen:

  • Die Kommunikation mit der GPU ist immer Singlethreaded, weshalb die FPS davon nicht profitieren.
  • Leider fallen jetzt auch einige „Lasten“ auf den selben Thread auf den die leistungshungrige Schwarmintelligenz zugeteilt wird (in diesem Bild #5495).
  • Die Schwarmintelligenz kann aber auch noch an diese Multithreadingtechnologie angepasst werden damit sie sich gleichmäßig über die Kerne verteilt
  • Die bessere Heuristik macht einiges aus, dennoch denke ich dass kein Weg an einer dynamischen Optimierung zur Laufzeit vorbeiführt, und ich glaube jetzt nicht das ich das sage aber ich glaube ich bin im Begriff so etwas wie einen Just in Time Optimizer zu entwickeln

Justin ?? → lat. Iustinus „der Gerechte“ (lt. Wikipedia)

Da hab ich mich verschrieben und die Autokorrektur hat sich wohl einen Scherz erlaubt :smiley:
Gemeint war: Just in Time Optimization :wink:

Raycasting, yeay! :smiley: Eigentlich ein älterer Algorithmus den ich mal geschrieben hatte. Ähnliches Prinzip wie bei Wolfenstein 3D, aber der Algorithmus ist nicht auf die damalige Hardware getrimmt.
Leider hab ich diesmal keine Statistik wie viele Rays pro Sekunde hier berechnet werden.

Und ganz wichtig: Die Berechnungszeit ist unabhängig von der Komplexität der Karte.

Auf einem alten SGS5 aufgenommen:

Fun Fact: Das waren 21.848 bis 148.645 Rays/s im Video.

Androidgeräte haben bekanntermaßen wenig Arbeitsspeicher. Die meisten Geräte haben um die ~1GB, von dem alle Apps, das Betriebssystem und natürlich der shared VRAM (2x 1920x1080 byte für den Bildschirm) versorgt werden müssen. Eine App bekommt da meist nur 64-256 mb an RAM zugewiesen.

Die Welt in GalaxyCommand ist in „Fließen“ aufgeteilt. Eine der Anforderungen ist es möglichst gigantische Karten im Speicher halten zu können. Wäre jede Fließe von einem Objekt repräsentiert, mit den erforderlichen Variablen und Subobjekten, gelangt man relativ schnell in Megabyte und Gigabyte Größenordnungen.

Zu diesem Zweck habe ich ein kleines Speicherverwaltungssystem gebaut. Jede Fließe wird von einem 32bit Integer in einem Array repräsentiert, in dem alle nötigen Variablen einkomprimitiert sind. Die Adressierung (Position und Größe) innerhalb des Integers wird zur Laufzeit festgelegt.
Eine 10.000x10.000 Welt benötigt dadurch nur noch 100 mb, statt 3 GB.
Von diesem System hängen der Renderer, die KI, der Wegfindungsalgorithmus und andere logische Operationen ab.

Weitere Variationen werden erzeugt in dem auch die Werte der Nachbarfelder interpretiert werden, um z.B. Ecken oder Kreuzungen von Wänden zu erzeugen, oder auch die Positon im Array um die verwendeten Texturdetails beim Rendern zu variieren.

Über die Performance lässt sich sicherlich streiten, ob die Bitoperationen länger benötigen als die Lookuptabellen von Objekten?

Im letzten Durchgang sah die globale Adressierung der Integer so aus:

  • 1 update-bool-bit für den Renderer
  • 11 Grundbefließung
  • 1 Objekt-Bool-bit
  • 1111111 Objekt Referenz
  • 11 Rotation
  • 1111111 Schadenswert des Objektes

Noch ist Luft nach oben. Um die 32 bit zu füllen :smiley:

Das steht als nächstes an:

  • Als nächstes wird nochmal das Speichersystem überarbeitet. Im Moment wird ein Dump des Kartenspeichers angelegt, das funktioniert natürlich nur solange sich die Adressierung nicht ändert. Die Werte sollen stattdessen umkomprimiert gespeichert werden. Dauert halt länger beim Lesen und Speichern.

  • Schadensmodell des Raumschiffes implementieren und Projektile abfeuern können.

Was aber manchmal durchaus etwas für sich hat. Zumindest am Anfang von einem Spiel ist mir die Kompatibilität von Spielständen egal. Hatte das ja bei Feature-Runner drin und irgendwann sehr viel Code welcher alte Spielstände versucht auf neue Formate zu übertragen (was nicht immer funktioniert hat). Und man kann seine Zeit echt sinnvoller nutzen, als einen sterbenden Use-Case zu fixen.

1 „Gefällt mir“