Browsergame: Umbau zu Firebase und Performance Optimierungen

Firebase als Push-Service in Laravel

Im letzten Beitrag habe ich über den Einbau von Push-Notifications in Laravel geschrieben. Dies hätte ja wunderbar in den gängigen Browsern als auch auf den Android Browsern Chrome und Firefox funktioniert. Aber eben nicht in den Webviews von Android die ich zum wrappen des Spieles nutze. Dazu der Beitrag Mobile Web App mit Cordova – Teil 2. Daher habe ich die Implementierung in Laravel umgebaut, besser gesagt ersetzt durch eine Firebase basierende Lösung. Auch hierfür gab, gibt es einen guten Beitrag auf der Website Medium.com. Dieser fällt zugegebenermaßen etwas kürzer aus, nichtsdestoweniger aber eine gute Hilfe war.

Alte Implementation der Push-Notifications rauswerfen

Um nun die Firebase Strategie zu implementieren war es nötig zu erst die alte Logikstruktur herauszuwerfen. Desweiteren wird in der neuen Logik die zusätzliche Tabelle zum Speichern nicht mehr verwendet was sie also obsolet machte.

Push-Notifications auf allen Geräten gleichzeitig oder nur auf dem zuletzt benutzten Gerät?

Die alte Logik ging davon aus dass Notifications eines Users auf mehreren Geräten angezeigt werden sollen oder können. Nach reiflicher Überlegung kam ich aber zu dem Schluss, dass ich dies eigentlich nicht benötigen würde oder wollte. Daher speichere ich nun den Zugangstoken ausschließlich einmalig in der User Tabelle, welche bei jeder neuen Subscription eines Users aktualisiert wird. Hieraus folgt, dass ein User natürlich nicht auf seinem Laptop beispielsweise und gleichzeitig auf dem Handy die Notifications angezeigt bekommt. Nach Überlegung wie die App bzw. das Spiel verwendet wird, dachte ich mir aber, dass dies die bessere und sinnvollere Lösung wäre.

Performance des Browsergames optimieren

Bisher hatte ich glücklicherweise nicht die Notwendigkeit Programmcode auf seine Ausführungszeit hin zu optimieren da in den Projekten in den ich gearbeitet habe dieser immer schnell genug war. Auch lokal hatte ich nie Probleme mit Online-Shops oder Webseiten diesbezüglich.

Flot auf live, langsam Lokal

Auf dem Server auf dem das Spiel läuft bzw laufen wird ist eben solches eigentlich sehr flott und lässt keine Wünsche Performance technisch offen. Warum also sollte ich die Performance wirklich optimieren müssen oder wollen? Problem hier ist, dass das Browsergame, welches sich mittlerweile zu einer doch recht programmtechnisch komplexen Wirtschaftssimulation entwickelt hat, relativ viele Livedaten in Echtzeit miteinander abgleichen und berechnen muss. dies führte dazu dass das Spiel auf dem Entwicklungsrechner Lokal, sei es mit wem oder der Level Box in docker oder anderen Containern in docker zu unangenehm langen Ladezeiten führte. Allein durch die Ressourcen Generierung und den Updates für die einzelnen Spielerwerte in Form von flotten Bewegung Bauaufträgen und anderem kam es auf dem Entwicklungsrechner zu Ladezeiten von bis zu 15 Sekunden. Dies hört sich jetzt nicht viel an ist aber zum Entwickeln eine Ewigkeit und eigentlich unzumutbar.

System unabhängig

Hierbei war es egal auf welchem System ich entwickelte. Sei es ein Linux System, Windows oder aber auch ein Gerät mit dem gammeligen angebissenen Apfel. Das Spiel war stockend langsam. Eine kurze Recherche bestätigte meine Vermutung, dass es Problem mit der Datenbank war. Es gab zwar viele Einträge die erahnen ließen, dass das Problem auch in der File Übertragung zwischen docker-container und festplatte liegen könnte. Was sich aber nach kurzer Analyse nicht bewahrheitete. Die einzelnen Seiten, die weniger Querys auf die Datenbank abfeuerten, waren deutlich schneller. Daher schaute ich an welchen Stellen ich unnötige Datenbankabfragen vermeiden konnte. Dies war an einigen Stellen möglich. So konnte ich einmal abgefragte Werte einfach an ausführende Funktionen weitergeben und musste nicht die gleichen Querys noch mal abfeuern.

Nightly Build – tolles Wort

Warum habe ich da nicht schon von Anfang an dran gedacht? Ich denke einfach es lag daran dass es eigentlich nicht notwendig war. Ich glaube ich weiß nun was nightly build heißt. Welche Optimierungen ich noch an den Abfragen vornehmen konnte möchte ich hier einmal kurz auflisten:

Statt mit Eloquent einen einzelnen Datensatz wie folgt abzufragen

Model::query()->where(...)->get()->first()

Zu

Model::query()->where(...)->first()

Doof was? Aber gut passiert.

Nicht immer braucht man ein query mit where Condition. Sofern das Model, die Tabelle bekannt ist und die ID des Datensatzes ebenfalls geht folgendes:

Model::query()->find(ID)

Auch hilfreich sind joins, inner joins als auch leftJoins. So kann man sich unnötig viele unterschiedliche Querys sparen und alle benötigten Daten mithilfe einer Abfrage holen. Wie kann so ein Join aussehen? Ein kleines Beispiel:

Planets::query()
    ->join('systems', 'systems.id', '=', 'planets.system')
    ->where('owner', $actUser->id)
    ->select('planets.*','systems.name as systemname','systems.xpos as systemxpos','systems.ypos as systemypos')
    ->get();

Du brauchst mehrere Conditions direkt im Join?

$UserStations   = UserStations::query()
    ->leftJoin('build_orders', function ($join) {
        $join->on('user_stations.planetid', '=', 'build_orders.planetid');
        $join->on('user_stations.slot', '=', 'build_orders.slot')->where('build_orders.sort',1);
    })
    ->where([
        ['user_stations.userid', $actUser->id],
        ['user_stations.planetid', $id]
    ])
    ->select('user_stations.*','build_orders.resttime','build_orders.timefull')
    ->get()

Klassenvariablen zum schnellen Zugriff

Klassenvariablen sind auch hilfreich. Innerhalb eines Aufruf kann es ja gut vorkommen, dass auf ein und die selbe Variable zugegriffen werden muss. Praktisch wenn die beim ersten Aufruf im Code dann einfach in eine Klassenvariablen gespeichert wird. So ist diese beim nächsten Aufruf einfach geprüft ob diese schon befüllt ist.

So dies waren ein paar Punkte. Aktuell bin ich nun von 15 Sekunden runter auf maximal 4 Sekunden. Das ist zwar noch lang, aber macht doch schon einiges her. Einfache Tricks, bzw. auch mal bei einem kleinen privat Projekt nicht schludern sondern effizient sein.