"Translating Quake 3 into Rust"

Címkék
The Rust-loving team at Immunant has been hard at work on C2Rust, a migration framework that takes the drudgery out of migrating to Rust. Our goal is to make safety improvements to the translated Rust automatically where we can, and help the programmer do the same where we cannot. First, however, we have to build a rock-solid translator that gets people up and running in Rust. Testing on small CLI programs gets old eventually, so we decided to try translating Quake 3 into Rust. After a couple of days, we were likely the first people to ever play Quake3 in Rust!

A teljes folyamatról szóló blogbejegyzés elolvasható itt.

Hozzászólások

Szerkesztve: 2021. 06. 13., v – 09:48

x

Nem fájdalmas, de van egy csomó modern, opensource Q3 motor, ami már a végletekig van optimalizálva, és nem az a szűk keresztmetszet, hogy C-ben vagy C++-ban van írva. Így ha átírod Rustra, nem sokat nyersz, teljesítményben sem, mert a modern klónok futási teljesítményét már a GPU határozza meg.

Egyébként meg Q3-klónokból sose elég, de ha valaki modern klónt akar írni, az ne a Rust-ra menjen rá, hanem Ray Tracing támogatás, Vulkan, SMP support, modern netes kód, stb.. Ezekkel lehet legjobban életben tartani egy ilyen régi játékmotort. Plusz ami még közösségi szinten sokat tud dobni rajta, hogy még legyen kedvük vele játszani, az a high-res textúrapakkok, nagy, modern pályák, stb.. Ezek sokkal többet adnak hozzá, mint az, hogy Rustban van írva.

“I didn’t start using Linux so I could have friends.” (Luke Smith, 2019) 🐧

Szerintem félreérted a motivációjukat, nekik nem a plusz teljesítmény, vagy a játékmotor életben tartása a céljuk, hanem a rust ige hírdetése. 1-2 éve olvastam, hogy valaki a CVS-t írja át rustba.

Ráadásul, egy ilyen játékmotor pont nem az a terület, ahol a szigorúan biztonságos memóriakezelés olyan nagy szerepet játszana.

„Kb. egy hónapja elkezdtem írni egy Coelho-emulátort, ami kattintásra generál random Coelho-kompatibilis tartalmat.”
"Az Antikrisztus ledarálja Mészáros Lőrincet."

C kódból RUST-ot csinál

Pontosabban Rust fordító által fordítható C FFI kódot csinál. Ezt a beágyazott C kóddal való interoperabilitás (de szép szó) kapcsán használjuk normál esetben.
Egyéb esetre ronda, kényszermegoldás. Viszont segíti a Rust-ra való átírást, hiszen a Rust fordító által lefordítható, így minden lépés után lehet futtatni, ellenőrizni.
Ebből kell majd még sok kézimunkával tényleges Rust kódot csinálni.

Lásd még: https://c2rust.com/

Egyszerű példa:

#include <stdio.h>

int main() {
    int res = 0;
    for (int i=1; i<=10; i++) {
       res += i;
       printf("%2d. szám: %3d\n", i, res);
    }
    return 0;
}

Transzformád át a fenti c2rust prorammal vagy weboldallal C FFI-s unsafe Rust kóddá, majd kezd el letisztázni. Végül letisztázás után kb. ezt kell hogy megkapd.

Egy fogalmi tévedést érdemes helyére tenni: unsafe kulcsszónak nem egyenes következménye, hogy nem biztonságos.
Mindössze annyit takar, hogy a nyelv a védőhálót kikapcsolja és teljes mértékben átadja a szoftver fejlesztőjének a kontrollt (és felelősséget). Alapból ilyen a C, safe módja sajnos nincs.

Azért nem tudja safe módban fordítani, mert nem normális Rust kódot generált a c2rust nevű cucc, hanem csak Rust fordító által fordítható C FFI kódot.
Amint látod, ha kézzel megigazítod a c2rust által generált valamit, akkor lehet belőle safe módon fordítható, normális Rust kódot csinálni. Kézzel.

ami már a végletekig van optimalizálva

Aha, meg ahogy azt Móriczka megálmodta. Egyrészt a multicore CPU támogatást rég elcseszték benne, másrészt a 20 évvel ezelőtti technológiákra lett optimalizálva, harmadrészt ha szerintem annyi geometriát, textúrát, fényt, stb. betolnánk neki, mint ami egy mai játékban van, szarabb FPS-t érnénk el, szarabb minőség mellett, mint egy modern enginevel, és akkor még nem is beszéltünk arról, hogy mi mennyit változott.

a doom meg a q3 engine, mivel opensource, kvazi minden ilyen 'vicces' portnak az alapja. pl, amikor macbook touchbar-on doom-ot futtattak. Vagy a raspberry pi-on. vagy okosoran.

a q3 siman azert, mert az egyfajta merce; ahol azt sikerult portolni, az mar "valami". egyfajta modern turing-teszt. :D

semmi koze jatekelmenyhez vagy hogy tenylegesen mukodo platform/port keletkezett volna, egyszeruen azt mutatja, hogy lam, ezt is tudja.

Szerkesztve: 2021. 06. 13., v – 10:31

Egyébként ezek az átírások azért is vannak, hogy finomhangolni tudják a nyelv gyenge pontjait.

A Rust-tal kapcsolatosan viszont sokunk véleménye megosztott. Tapasztalatom:
   - Rust-ban lehet gyorsan futó kódot írni.
   - Rust-ban könnyű bloat-ot csinálni, erősen támogatja a "gyors" fejlesztést
             (crates.io - rántsd be onnan modult, amely függőségként újabb modulokat ránt be, ...).

A kettő közötti egyensúlyt félő hogy kevesen fogják elsajátítani, így a gyakorlatban több erőforrás pazarló szoftver keletkezhet.
Amint írtam, lehet gyorsan futó kódot írni benne, de ehhez oda kell erre a szempontra is figyelni.

A gond nem is ez, hanem hogy egy dev team-en belul a skill szint mindig kulonbozik. Es amikor a pro dev meglatja, mit csinalt a kodjaval a junior, sikitva menekul.

A java-t ezert szeretik enterprise kozegben. A pro java dev es a junior java dev kozott nincs akkora egetvero kulonbseg, mint pl. scala vagy perl -ben. Kevesbe jellemzo a sikitva menekules.

A rust IMHO valahol a scala es a java kozott van feluton a 'skill inequality' skalan, de az is igaz, hogy amig scala-ban evek kellenek, mire junior ugy-ahogy megizmosodik, addig rust-ban ez azert jocskan gyorsabban is megy.

"nincs akkora egetvero kulonbseg" Akkor mar inkabb Go, mert ott simplicity a vezer evl. De amugy meg a pro devnek is ugy kell fejleszteni, hogy a junior is megertse. Ez az enterprise fejlesztes lenyege, hogy odaul a masik es 10-20-40 perc mulva ott folytatja, ahol a masik abbahagyta. Es igen sokan hiszik magukat pro-nak es hasznalnak ki minden nyelvi featuret, de nem ettol lesznek pro-k. hanem attol, hogy szog egyszeruen is meg tudjak foglamazni, amit szeretnenek.

En pl ezert hagytam fel a Haskell tanulassal, mert abban a kozegben, ahol dolgozom nem elfogadhato, hogy a szerzon kivul mas nem erti mi van odairva, es ket het mulva o sem :D Pl Go kodot nagyon kenyelmes reviewzni.

Aztan a dolgok bonyolultta valnak, ha solid elveket akar valaki kovetni, vagy pure functionoket akkar irni, hogy valos unit teszteket, ne csak cross package testet lehessen implementalni.

Nekem ez a ket fajo pont van, unit tesztek es hibakezeles (marmint ha konkret hibat akarsz kezelni, nem csak if err return err).

ne is mondd. mikor meglattam, hogy rust-ban nincs exception meg throw, es kezzel kell dobalni. vagy kenytelen leszel Result -ot hasznalni mindenhol es akkor mehet a ? operator, de az meg eleg invaziv, meg reszemrol ruhellem, hogy a method signature emiatt kevesbe olvashato.

exception jo dolog. ott kezeled a hibat, ahol ertelme van, nem kell 'felbuborekoltatni' kezzel.

En mar a Javat is elfelejtettem ;) De azert ne felejtsuk el, a runtime exceptionnek van egy igen nagy hatulutoje: RUNTIME. Sehol nincs jelolve, sehol nincs kikenyszeritve es amig elesben elo nem jon, addig akar rejtve is maradhat (neha jo sokaig), plusz megjelennek a @Test(expected=RuntimeException.class) tesztek, aztan lehet nagyokat pislogni, mikor kiderul, hogy nem is ott hasal el teszt ahol a kolto gondolta :)

rust-ban nincs exception meg throw

Ahogy írtad, van ? operátor, ellenben lekezelheted helyben is. Erre több lehetőséged is van:

1.  if let Ok(x) = cuccod() { ... }. Lásd: https://doc.rust-lang.org/std/result/#the-question-mark-operator-
2.  match cuccod() { Ok(x) => ..., ... }. Lásd: https://doc.rust-lang.org/std/result/
3.  unwrap_* és or_else(), stb. metódusok például https://doc.rust-lang.org/std/result/enum.Result.html#method.or_else

ezt ertem es tudom, de ettol nem lesz olvashatobb a kod :)

exception-nel ezek kozul egyik sem kell. eleg a kulso vagy modul interface-nel egy catch, es kesz. sokkal atlathatobb. mert a hiba alapvetoen a kivetel, nem a szabaly (innen a nev is, ugye). nem a kivetelekre tervezzuk meg a kodot, hanem a happy flow-ra.

Aztan persze van, ahol ez nem eleg, pl. mert a hiba egyaltalan nem ritka, es resze az API-nak. akkor persze java-ban is kenytelen vagy Result-ot hasznalni meg hasonlok. De ez ritka; az altalam latott rendszerek 80-90%-aban boven eleg volt az egyszeru exception alapu hibakezeles.

crates.io - rántsd be onnan modult, amely függőségként újabb modulokat ránt be, ...

Ez miben más, mint berántani egy akármilyen natív libet C/C++ alá? Inkább afelé kellene haladni, hogy ha már berántunk valamit az kicsi legyen, hogy ne rántsa be maga alá a fél világot. Ilyen szempontból egyébként a Node.js világ szerintem jó példa, ahol sok library (és framework) követi a single responsibility elvét, nem ritkán egyetlen függvényt megvalósítva. Szerintem ez előremutatóbb, mint berántani egy nagy frameworköt ami mindent is tud, amiből jó, ha 20%-ot használsz.

Igen és nem. Nyilván nem mindegyik library, és főleg framework fogja követni ezt az elvet. Illetve a Node.js-nek van egy olyan egyedi tulajdonsága, hogy egy függőség több verziója is használatban lehet ugyanabban a projectben a tranzitív függőségeken keresztül. Pl. ez a függőségfa Node.js-ben érvényes:

  • my-project 1.0.0
    • a-lib 1.0.0
      • c-lib 1.0.0
    • b-lib 2.0.0
      • c-lib 2.0.0
    • c-lib 3.0.0

Az npm valamennyire tud segíteni a függőségek deduplikálásával, de a fenti esetben a c-lib mindhárom verziója megtalálható lesz a node_modules könyvtárban.

Továbbá a libraryk általában magukkal csomagolják a saját tesztjeiket, dokumentációjukat, stb. is, amit jó eséllyel soha nem fogsz használni. Ha a release-edet Webpack-kal vagy más bundlerrel állítod elő akkor ez nem gond, mert az eleve csak azt rakja bele a buildbe amit használsz.

"Illetve a Node.js-nek van egy olyan egyedi tulajdonsága, hogy egy függőség több verziója is használatban lehet ugyanabban a projectben a tranzitív függőségeken keresztül."

sztem ez nem egyedi, ez a java-ban is komoly probléma tud lenni ha nem tapasztalt a fejlesztő

Nem, Javaban akkor fel kell oldani a verzióütközést, vagy trükközni kell classloaderekkel. Node.js-ben erre nincs szükség, itt nincs ütközés, itt egyszerre lehet használatban több verzió

Szerk: Illetve, nem tudom Java 9 óta létező modulokkal feloldható-e a probléma.

Egyszer nekem valaki azt mondta, hogy igen, mert minden modul külön classloader, de sosem próbáltam... 1-2 appot próbáltam modularizálni anno, de az idea nagyon nem akart benne segíteni, valami automatic moduleos bug miatt nem ment a spring boot projekt indítása, azóta meg nem is próbáltam. 

*altalaban* nem gond a verzioutkozes, mert a lib-ek nagyjabol backwards compatible-k, vagy update-elik oket rendesen.

ha megse (fyi, ez ritka, hacsak nem valami jocsi-bacsi-1.0 libet hasznalsz), akkor ott a shading. berakod egy kulon modul-ba, dependencykkel egyutt, es a shader plugin at tudja mozgatni egy masik package ala, igy nem fog utkozni az ujabb verzioval. Mi pl. egy osregi protobuf-fal csinaltuk ezt, mukodik.

IDEA nem ismeri a shader plugin-t, ugy egyaltalan nem, szoval a modult, amiben shade-elsz, nem szabad betolteni vele. Legjobb, ha teljesen kulon rakod, monduk egy 'jozsi-bacsi-1.0-shaded' -be, es a helyi repository-ba feltoltod. fire&forget!