Exoframe-Autonavigation und Refactoring in einen Workspace

Hinweis: Dieser Blogpost wurde maschinell aus dem englischen Original übersetzt. Zum englischen Original.

Überblick

Der letzte Monat war eine Mischung aus Begeisterung und zähem Kram. Begeisterung, weil ein sehr cooles, essenzielles Game-Feature dazugekommen ist. Zäher Kram, weil absurd viel Arbeit nötig war, um Exofactory von einer einzelnen Crate in ein Workspace-basiertes Projekt zu refactoren.

Obendrauf hatte ich eigentlich geplant, noch ein bisschen mehr zu machen, aber mit der massiven historischen Juni-Hitzewelle war es schwierig, in einem heißen Büro, in dem die überarbeitete, unterbezahlte Klimaanlage nicht hinterherkam, den Willen für mehr als die "heute nicht verhungern" Arbeit aufzubringen.

Trotzdem zählt das Exoframes-als-Agents-Feature, glaube ich, eher in die Quality-over-Quantity-Kategorie von Blogpost. Bin ziemlich stolz drauf.

Zusätzlich dazu gehe ich auf die Arbeit ein, die ich ins Refactoring des Spiels von einer einzelnen Rust-Crate in einen Workspace gesteckt habe. Ich beschreibe, welche Schritte ich gemacht habe, warum, wie, und wie andere das vielleicht angehen könnten.

Wie immer: Wenn du diesen Post interessant findest, würde ich empfehlen, das Spiel auf Steam auf die Wunschliste zu setzen.

Exoframes als Agents

Die Exoframe-UI. Jetzt mit eigener Namensgebung!
Die Exoframe-UI. Jetzt mit eigener Namensgebung!

In Exofactory werden Quality-of-Life-Upgrades freigeschaltet, während Spielende beim Aufbau ihrer Infrastruktur Fortschritte machen.

In einem früheren Post habe ich das Platzieren von Gebäuden im globalen Modus behandelt. Exoframes als Agents handeln zu lassen, ist eine Klasse von Quality-of-Life-Upgrades, die freigeschaltet werden kann, während man weiterkommt.

Im Grunde können Exoframes automatisch mit mehreren Dingen beauftragt werden, durch die Welt navigieren und mit ihr interagieren, ohne dass "bewusste" Kontrolle nötig ist.

Technisch wurde das mit Hilfe von zwei hervorragenden Crates aus dem Bevy-Ökosystem umgesetzt, nämlich bevy_rerecast und bevy_landmass.

Bevy Rerecast baut und aktualisiert einen Navigationsgraphen basierend auf dem Recast-Algorithmus und kann, erstaunlicherweise, einfach direkt die bestehenden Avian-Meshes verwenden. Unfassbar praktisch.

Nachdem der Navigationsgraph "gelöst" war, war der nächste Schritt, diesen Graphen tatsächlich zu verwenden, um Dinge herumzubewegen.

Beim Blick ins Ökosystem habe ich zwei mögliche Libraries in Betracht gezogen: vleue_navigator und landmass_rerecast.

Nach meinem besten Verständnis ist vleue_navigator eine Low-Level-Navigation-Library, die feinere Kontrolle gibt, während landmass_rerecast eine High-Level-Navigation-Library ist, die die Steuerung in ein höheres "Agents"-Konzept abstrahiert. Wenn man die Library benutzt, konzentriert man sich mehr auf Ziele statt auf Navigationsdetails.

Angesichts des Namens und der Überschrift dieses Abschnitts hast du vielleicht richtig geraten, dass ich mich für landmass_rerecast entschieden habe, weil es zu dem Ansatz passt, den ich gehen wollte, und gleichzeitig zum In-Game-Verständnis davon, wie Exoframes gesteuert werden könnten. (aka unterbewusste Subprozess-Agents)

Es lief ziemlich reibungslos, sogar mit sauberer Anbindung an bevy-tnua. Cool daran ist, dass Agent-Exoframes dadurch über denselben Codepfad navigieren und sich bewegen, den auch Spielende verwenden würden.

Von hier aus habe ich ein paar einfache Components verdrahtet, um Exoframes zu markieren, die als Agents handeln sollen, und dann etwas getestet.

Exoframes können automatisch herbeigerufen oder bewegt werden.

Fortschritt.

Danach wurden Item-Fetching und Requests zusammen mit einer einfachen UI drumherum umgesetzt.

Wir können Waren in manchen Exoframes anfragen und sie von anderen liefern lassen.
Wir können Waren in manchen Exoframes anfragen und sie von anderen liefern lassen.

Dann das letzte Stück: ein neues Dashboard, in dem Spielende alle bestehenden Exoframes konfigurieren können. Hoffentlich ist das die letzte UI, die ich jemals ohne Bevy Scene Notation, aka BSN schreibe.

Wir können Exoframe-Einstellungen global toggeln.
Wir können Exoframe-Einstellungen global toggeln.

Am Ende können wir jetzt Exoframes herbeirufen, Requests stellen und einen Pool von Exoframes festlegen, zu denen gewechselt werden kann oder die als Fetcher genutzt werden.

Exoframes können Ressourcen an andere Exoframes liefern.

Das alles ist definitiv sehr cool, aber ich glaube, der wichtigste Takeaway hier ist, dass all diese Arbeit nur durch das hervorragende, stark interoperable Bevy-Ökosystem möglich war. Leute, die Crates schreiben, kennen andere Crates und arbeiten daran, dass die Developer Experience smooth und sehr ergonomisch ist.

Selbst wenn sich ein Exoframe bewegt, wird das gehandhabt.

Es gibt eine Menge fantastischer Bevy-Crates da draußen. Ich empfehle, sie entweder über die offizielle Bevy-Asset-Seite oder, als zweite Option, über mein persönliches BevyDex-Projekt zu erkunden.

Codebase-Refactor

Ein Chart mit Exofactory-Build-Zeiten. Man sieht, dass fast die gesamte Zeit in die Exofactory-Lib geht

Der andere große Arbeitsblock drehte sich um ein größeres Refactoring des Spiels von einer einzelnen Crate zu einem Workspace aus Crates. Der Grund dafür war, dass ich an dem Punkt angekommen war, an dem selbst eine kleine Änderung, sogar eine einzige const-Änderung, zu nervig langen Compile-Zeiten geführt hat, sogar bei Dev-Builds. Man kann nur begrenzt oft 45 Sekunden auf einen Dev-Build warten, während man Walking-Wobble-Consts tuned, bevor man entscheidet, dass etwas passieren muss. Mach daraus 1:10 für Release-Builds, um tatsächlich zu bestätigen, dass diese Consts gut aussehen.

Fühlt sich noch schlimmer an, wenn man all die idle Cores sieht, während einer bei 100% festhängt. Fühlt sich nach Verschwendung an.

Unten ist eine vereinfachte Beschreibung des Ansatzes, mit dem ich das angegangen bin.

Meine Strategie

Mein Plan war, einfach jedes der "Root-Level"-Bevy-Plugins in eine eigene Workspace-Crate herauszuziehen. Ich hatte mich auf saubere Root-Level-Plugin-Isolation konzentriert, also waren die meisten Plugins ziemlich einfach zu verschieben. Einfach sehr viele Import-Änderungen.

Refactor - Vor dem Refactor. Zirkuläre Imports.

Das Problem war, dass es mehrere Gruppen von Bevy-Plugins gab, die Components und Resources voneinander importierten. Oben ist ein Beispiel für die Art von Dingen, die ich gesehen habe. Diese Ausnahmen, die ich gemacht hatte, waren die Ursache von sehr viel Schmerz und Leid. Es gab ein paar potenzielle zirkuläre Dependencies, die verhindert werden mussten. ABER ich wollte wirklich so viel parallele Kompilierung wie möglich erlauben, also wollte ich den Workspace so flach wie möglich halten. Workspace-Crates sollten das absolute Minimum an Intra-Workspace-Dependencies haben.

Hier habe ich einen gestuften Ansatz gewählt. Stage 0 war, einfach alle einfachen Root-Level-Plugins, die keine Änderungen brauchten, in eigene Workspace-Crates zu verschieben.

Stage 1 - Shared Components isolieren

Refactor - Components und Messages herausgezogen

Der erste Schritt war, die geteilten Components/Resources/Message-Typen usw. aus den Plugins heraus in eigene Workspace-Crates zu verschieben und nur die Imports entsprechend anzupassen. Ich habe das gemacht, weil ich Dinge inkrementell herausziehen wollte, ohne das Spiel lange kaputt machen zu müssen.

Ich habe den Workspace hinzugefügt, die exo_* Crates hinzugefügt und die relevanten Items rübergezogen.

Stage 2 - Systems verschieben

Refactor - Nach dem Refactor. In mehrere Crates aufgeteilt

Hier wurde es etwas knifflig. Ich musste die Systems aus der Main-Crate herausziehen. Das Problem war, dass die geteilten Components weiterhin überall gebraucht wurden, was die parallele Kompilierung reduziert.

Das Ergebnis, für das ich mich entschieden habe, war, die stark geteilten Components in den relevanten Crates zu behalten und einfach überall Dependencies auf sie hinzuzufügen, wo sie gebraucht wurden. Solange ich diese bei aufeinanderfolgenden Compiles nicht ändere, bekomme ich trotzdem die parallele Arbeit. Da diese Core-Components nicht oft geändert werden (oder in manchen Fällen gar nicht), war das akzeptabel.

Die Plugins und Systems, die eher Änderungen ausgesetzt sind, wurden in exo_*_runtime Crates verschoben.

Das führte zu einer kleinen Handvoll Bevy-Plugins, die über Runtime-/Non-Runtime-Crates aufgeteilt waren, was nervig wirkt, aber es ist ein Kompromiss, mit dem ich okay war.

Außerdem ist der Dependency-Graph der Workspace-Crates definitiv nicht flach. Ich habe auf so flach wie möglich gezielt, aber ich wollte vermeiden, zu viel neu zu schreiben. Es ist aber definitiv VIEL besser.

Ergebnisse nach dem Refactor

Ein Chart mit den Build-Zeiten nach dem Refactor. Viel mehr parallele Arbeit.

Der Graph oben ist ein frischer Rebuild nach einem cargo clean. Runter von 1:10 auf 45 Sekunden für einen vollständigen Release-Build von Grund auf. Seit ich diese Compile-Zeiten aufgenommen habe, wurde noch mehr aus der "main" Exofactory-Crate herausgezogen, aber ich habe seitdem auch Features hinzugefügt, also ist es kein sauberer Vergleich mehr.

Der größere Gewinn kommt aber beim tatsächlichen Arbeiten am Spiel. Es ist selten, dass ich bei der Arbeit mehr als eine oder, im schlimmsten Fall, ein paar Crates gleichzeitig anfasse. Das bedeutet, dass ich Dev-Builds in ein paar Sekunden bekomme und Release-Builds in ein paar Sekunden mehr.

Eine enorme Quality-of-Life-Verbesserung. Und ich kann auf meinem viel schwächeren Laptop tatsächlich arbeiten.

Ich bin mir nicht sicher, ob ich dieses Refactoring auf die effizienteste Weise durchgezogen habe, oder sogar auf die Weise, die die bestmöglichen Ergebnisse erzeugt hat. Wenn jemand etwas Dummes sieht, das ich gemacht habe, bitte sagt mir Bescheid. Trotzdem habe ich meine Ziele erreicht, nämlich:

Mein Rat

Mit den Caveats oben im Kopf: Wenn ich meinem früheren Ich (oder anderen) Rat geben müsste, würde ich es darauf runterbrechen:

Diese drei Punkte hätten mich mit der gewünschten Geschwindigkeit am Spiel arbeiten lassen und gleichzeitig erlaubt, das Spiel später ohne Schmerzen in einen Workspace zu refactoren.

Was den Zeitpunkt für die Migration von einer einzelnen Game-Crate zu einem Workspace angeht, glaube ich, dass die Hauptsache ist, es nicht zu überdenken. Wenn Kompilieren und Testen nervig wird, ist es Zeit. Du weißt es, wenn du es fühlst.

Fazit

Richtig cool, die kleinen Exoframes im Spiel herumlaufen zu sehen. Für mich war das ein "Das ist wirklich cool" Moment. Ich glaube, das Zusammenspiel der ganzen Bevy-Ökosystem-Crates hat es viel einfacher gemacht, als es eigentlich hätte sein sollen.

Sehr froh, mit dem Refactor fertig zu sein. Ich werde auf der Bornhack coden, campen und einen Bevy-Workshop halten, also ist es ein guter Zeitpunkt, vom Laptop aus am Spiel arbeiten zu können.

Wenn wir uns dort sehen, sag hi!

Zum Schluss, wieder einmal: Wenn du glaubst, dass dich das Spiel interessiert, oder du einfach deine Unterstützung für das Spiel zeigen willst, hilft es enorm, das Spiel auf Steam auf die Wunschliste zu setzen.