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
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.
Fortschritt.
Danach wurden Item-Fetching und Requests zusammen mit einer einfachen UI drumherum umgesetzt.
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.
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.
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.
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

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.

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

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

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

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:
- Schnelle Compile-Zeiten
- Minimale Logikänderungen
- Crate-Trennung auf Bevy-Plugin-Ebene (größtenteils)
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:
- Fang mit einer Crate an und mach dort alles. Kein Bedarf für vorauseilende Optimierung.
- Sei 100%, vollständig, komplett, ohne Quatsch strikt bei Plugin-Isolation. Kein Plugin sollte jemals irgendetwas aus irgendeinem anderen Plugin importieren.
- Alle Plugin-Imports sollten entweder aus diesem Plugin kommen, oder aus unabhängigen Nicht-Plugin-Structs, die von anderen Plugins unabhängig sind.
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.