Chápu snahu o jednoduchost, ale co separace grafiky od herní logiky? ;-) . Hodit všechno do jedné struktury gameState je dost velký mix.
Kdysi hodně dávno jsem napsal v SDL (později upraveno na SDL2) hračku wormik: https://github.com/kvr000/wormik.git , dokonce ji někdo dostal do nějaké distribuce (teď bych ji musel asi trochu oprášit, aby fungovala, jsou tam nějaké hardcoded cesty k resources a fontům apod). Port SDL na SDL2 nebyl úplně přímočarý, ale zase žádná velká tragédie (matně si vzpomínám, že největší problém bylo SDL2-ttf, které stále pracovalo s původním software surface). V každém případě SDL2 je asi logicky jediná volba dnes.
Je otázka, zda 2D grafika má ještě moc velký smysl, na LCD se pak stejně mění scaling a výkonově je to asi už jedno (nebo možná i horší?). Přišlo mi, že ty různé alpha blending apod nejsou úplně přímočaré a hlavně závislé na kontextu, takže bych si tipnul, že možná přijde další upgrade, až budou chtít využít Vulkan backend (ale zkušenosti s 3D mám malé, tak je to jenom odhad).
Jinak mi zatím z uvedených příkladů nepřijde až tak velký rozdíl mezi Go a C++, obzvláště, když se původní "defer delete whatever" přesunul do jedné funkce, která ještě možná není korektní v případě, že se povede jen částečná inicializace. Čímž nechcu podceňovat možnosti jazyka, ale tady bude ta logika docela jednoduchá na to, aby nějak výrazně těch vlastností využila...
Jj s SDL (jednickou) uz dneska mohou byt i problemy pri instalaci zavislosti, dnes uz jen 2.
SDL2, kdyz se pouziji textury, renderuje taky pres texturovaci jednotku GPU, takze tam rozdil nebude. I pri modulaci, scalingu atd.
Rozdily mezi Go a C++ (nebo radeji spis C, protoze SDL je ceckovina) jsou treba v jednodussim handlingu chyb - proste chyba je bud nil nebo struktura s jejim obsahem. Nemusi se resit, jestli SDL_Init vraci zaporne cislo, funkce pro ziskani surface NULL a mraky dalsich vyjimek. Je to mozna malickost, ale pise se mi takto mnohem rychleji. Taky je fajn, ze proste muzu udelat strukturu ve funkci a vratit ukazatel na ni (tedy fajn - neni zapotrebi na to myslet).
Jinak je samozrejme pravda, ze pro takto primitivni veci se moc rozdilu nepozna. Zkusim nahodit to reseni s gorutina pro NPC, to mozna bude flame :)
PS: castecna inicializace - asi myslite, ze si nekdo zachyti panic() a bude pokracovat dal? Jj to muze nastat.
Tou částečnou inicializací jsem myslel, že třeba něco z SDL init, create window a create surface uspěje a něco ne. Finalize se v takovém případě nezavolá (v případě poslední varianty kódu). Je pravda, že ty objekty stejně posbírá GC v nějakém náhodném pořadí, ale korektní by bylo ty resources pozavírat slušně v opačném pořadí a pak teprve provést panic a "návrat" z funkce. Takže ta jednoduchost je zčásti za cenu korektnosti :-) . Vrácená chyba asi něco ušetří, ale stejně by byla volána na dalším řádku, který tu chybu reportuje jako SDL_error() nebo něco podobného. Exceptions s pouze optimistickým blokem by asi byly nejjednodušší, i když samozřejmě stále by měly uzavírat resources korektně. Ošetřování chyb je vždycky peklo :-)
Ohledně scalingu apod - jasně, performance asi bude celkem jedno, zvláště u takových hříček. Problém je, že 640x480 na celou obrazovku neodpovídá rozlišení LCD a původní hra předpokládá, že obrázek příšery nebo jiných ikon se mapuje 1:1 ze zdrojového obrázku na obrazovku. Třeba zmiňovaný Wormik používá asi 16x16 ikonky a celkové rozlišení 640x480 (počítám, že tady to skončí podobně). Kdyby se automaticky upravila velikost obrazovky podle rozlišení, mohly by být původní ikony větší a vyhlazovat se podle skutečného rozlišení. V zásadě je to pořád 2D, ale takové dynamičtější :-)
Ty goroutines jsou docela zajímavý nápad. V normální (C) hře by to asi zabilo výkon mutexama, ale Go s GC si může hrát s funkcionálními vlastnostmi a se snapshoty stavu, takže jednotlivé objekty se mezi sebou nemusí nutně synchronizovat. V podstatě stejně jako multiplayer po síti, akorát tady v rámci stejného runtime. Držím palce!
V té poslední verzi ale právě ty defer chybí. Místo toho je tam jedna finalize funkce, která se zavolá v případě, že celá inicializace skončila úspěšná. Obecně, ta výhoda defer se ztrácí, pokud se má výsledek vrátit a defer-ovat pouze v případě neúspěchu.
Ohledně GCD - mutexy byly myšleny na modifikaci sdíleného stavu jednotlivých objektů, to GCD nezachrání (pokud nebudu schopen v předstihu identifikovat, které objekty se mohou dostat do konfliktu). Ale možnost pracovat se snapshotem, který může potenciálně být outdated, by věc zjednodušit mohla. V C by se to dělalo o dost hůř, neboť by se musela řešit životnost snapshotu, což znamená opět nějakou synchronizaci. GC jazyky to zvládnou líp.
Jo, to s tím finalize jsem přehlédl. Tam by se skutečně mělo testovat na nil.
V GCD lze mít hodně front a určit serializovatelnost, když výběru serial namísto concurrent, žádné dva tasky nepoběží v rámci fronty zároveň (teoreticky na jednom vlákně, ale to si interně rozhoduje knihovna). Popravdě nevím, jestli to je efektivnější než konkurentní fronta a mutexy.
K tempu článku dost dobře zpětnou vazbu dát nemohu, nejsem tak úplně cílovka. Nicméně pár drobností tu mám, takže nejprve bych chtěl zdůraznit, že celkově mi článek přijde super, tedy následující kritika nemá za cíl shodit celý text, jen se dotknout problémů, které v něm vidím.
„[...] díky tomu, že mnoho programovacích jazyků nabízí programátorům automatickou správu paměti (garbage collector) se stavový prostor mnohdy dosti razantním způsobem zmenšuje (nemusíme u všech objektů řešit, kdo je jejich vlastník a kdo je může uvolnit z operační paměti) [...]“
Předpokládám, že to tak nebylo myšleno, ale tato část může evokovat dojem, že vlastníka a uvolnění objektů z paměti nemusíme řešit vůbec. Což je právě naopak - jestliže děláme hru v jazyce, který má GC, tak stavět okolo tohoto předpokladu je vyloženě cesta do pekla, které nastane ve chvíli, kdy dojde projekt do oné optimalizační fáze, kdy je potřeba začít zjišťovat, co je v paměti a co ne a proč to tam ještě je. Jasně, v ideálním světě se tato fáze děje celou dobu vývoje, ale hry (kód, data, design, ...) se vyvíjí tak rapidním způsobem, že to není tak úplně reálné.
Takže zde máme fázi rapidního vývoje, kdy se celá hra neustále mění dost zásadním způsobem, pak se přechází do fáze ladění mechanik a až pak nastávají větší optimalizace. A to, podle jakých principů byly vyvíjeny první fáze, se zúčtuje v těch posledních - a to přesně podle poučky, že cena bugu roste s časem exponenciálně.
To, že neřešíme životnost krátkodobých objektů (dočasné objekty a pole), je ok (ale nesmí jich být moc, jinak GC zaseká celou hru - ideální, ale nereálné, je nula alokací během snímku). Ale hned od začátku je vyloženě nutnost řešit život assetů - textury, modely, zvuky, fonty, atd. Musí být naprosto jasné a deterministické, kdy jsou potřeba a kdy už ne.
„func [...] redraw() { [...] moveNPC(state) [...] }“
Tuto chybu jsem naposledy udělal před mnoha lety v mých neslavných začátcích :D. Opravdu, opravdu, opravdu není dobrý nápad aktualizovat stav hry ve funkci pro kreslení scény. Ze začátku se to může zdát v pohodě, jenže pak člověk narazí na spousty problémů, které to způsobuje (např. pauzování hry). Jistě, v triviálním příkladě ze článku je oprava značně jednoduchá - prostě to volání přesunout z redraw() do eventLoop() hned před state.redraw().
Jenže článek se mi jeví, jako pro začátečníky (když už ne v rámci programování, tak v rámci vývoje her) a přijde mi trochu nešťastné jim ukazovat toto řešení, které jim může ulpět v hlavě ... a pak zapláčou, jako kdysi já.
A jistě, lze namítnout, že v případě potřeby je možno kód refaktorovat. Vlastně to při vývoji her považuji za naprostou nutnost - ale to už je jen osobní názor, někteří lidi z oboru s tím nesouhlasí. Jenže jestliže beru v potaz cílení na začátečníky - při překročení určité složitosti toho prostě nejsou schopni. Buď projekt doklepou s vachrlatými základy, které mají, nebo začnou znovu od nuly. Opět vzpomínám na své dávné zkušenosti a tehdejší neschopnost dotáhnout hru do konce.
GC v Go nic nezaseká, má pauzy pod 1 ms. Ideální by sice bylo nula alokací na haldě (jako v případě SW pro vesmírné lety :-), nicméně v praxi funguje escape analýza v Go poměrně dobře a počet alokací na haldě dost redukuje (rozhodně není dokonalá, ale GC problém nebude ani při tisíci objektů na haldě). Přesto mi ale nepřijde jako dobrá volba psát hry v Go, byť jistě to jde (stejně jako v Prologu).
Není, ale ve většině případů (99%) je pod 1 ms. Limit, který runtime prakticky nikdy nepřekročí, je 10 ms. Ovšem pozor, tisíce větších řetězců jsou hračka, v takovém případě jsou typické pauzy v řádu desítek μs. GC v Go se přestává flákat až u hald s velikostí v řádu desítek GB, což asi není úplně typické, určitě ne u her, které běhaly na osmibitech s 16 kB paměti.
Go ve skutečnosti vůbec neznám takže nemohu nic tvrdit na jisto. Nicméně pokud jeho gc nedokáže běžet paralelně spolu s hlavním kódem aplikace nebo pokud gc nemá (alespoň téměř) konstantní časovou složitost, potom tvrzení „má pauzy pod 1 ms“ moc nevěřím. Netvrdím, že tomu tak není, jen se mi to zdá být nepravděpodobné (a to i třeba ve světle tohoto článku). Věřím, že tomu tak může být u hromady běžných aplikací, menších a třeba středních her, ale pokud není splněný alespoň jeden předpoklad výše, tak zde musí být limit. A velké hry ten limit velice rády zkouší. Nicméně pokud by byl Go-GC řádově cca stokrát rychlejší, než .net-GC, pak uznávám, že i pro větší hry by nejspíš nemusel být problémem.
P.S. U Go by u větších her byl problém spíše s tím, že je celkově o něco pomalejší než C(++), ale GC ve verzi Go 1.14 určitě ne. Existují různé limitní případy, na které člověk narazí, když hodně “blbne”, ale jinak jak jsem psal výše, některé firmy mají v aplikacích v Go na haldě třeba 50 GB a latence zůstává nízká (o tom byl někde hezký článek, jak autoři nějaké nenažrané aplikace v Go 1.2-1.4 bombardovali tvůrce jazyka stížnostmi na latence v řádu sekund tak dlouho, až celý GC přepsali s důrazem na minimální latenci — v nedávném porovnání s GC v Javě Go Javu zašlapalo do země :).
Já to taky nemůžu na základě vlastních zkušeností tvrdit na 100%, tak velké haldy nepoužívám a dočasných objektů max. desetitisíce napříč vlákny na serverech. Je to tvrzení tvůrců Go, ovšem podle informací uživatelů (firem) pravdivé. Zvlášť to srovnání s Javou je zajímavé čtení. Člověk ovšem nesmí používat finalizéry, to je pak cesta do pekel.
BTW ten článek o přechodu na Rust se dost probíral, kromě použití staré verze Go (odtud ony CPU “spikes”) to prostě měli blbě napsané. To ovšem nic nemění na faktu, že v Rustu bude správa paměti vždy výkonnější.
K tomu Go - ano urcite to neni jazyk pro ackove tituly. Ale pro kluky, kteri si chteji neco vyzkouset to IMHO neni spatne reseni. Muzou totiz zkusit i Pygame nebo Pyglet a tam jsme s vykonem prakticky o rad jinde :) Nebo si zkusit neco napsat v JS, ale to je tema, kteremu se z mnoha duvodu nechci venovat :)
Na druhou stranu - zkusit si nastrukturovat nejakou hru s mnoha NPC s gorutinami by mohlo byt zajimave. Nezkousel jsem to, ale pouzit gorutiny de facto ve funkci korutin by to mohlo zprehlednit (nebo taky totalne zasekat).
Super, zaprvé moc díky za feedback, který je k věci.
Tu první část jsem myslel trošku jinak, ale asi to vyznělo špatně. Já totiž nezmínil úplný kontext a ten je tento: v dnešní trošku zmatené době se mi ozvali kluci, co sice umí (teoreticky) programovat, protože se to učili ve škole, ale chtějí si kromě nějakého bubble sortu a podobných textových věcí vyzkoušet i nějakou jednoduchou hru. Což je skvělé, že se jen neválí doma (tedy oni se asi doma válí, ale u IDEčka :-).
No a z praxe vím, že je fakt největší problém s udržením stavu aplikace - to je IMHO mnohem důležitější věc na zmínění (při výuce), než důraz na řešení konkrétních algoritmů. Protože ty algoritmy člověk dříve či později nějak odladí, ale to bývá malá a izolovaná věc, kdežto stav se ve větší aplikaci "rozleze" všude a těžko se řídí. S tím GC to asi bylo napsáno nešťastně, protože v kontextu her (a to nemluvíme o áčkových titulech, ale o jednodušších věcech typu tower defense) je to pochopitelně problém, jak sám píšete - ovšem v Go ne tak dobře řešitelný jako v C/C++.
Pozn: ono se "díky" rychlému GC v Go a taky toho, že má alokaci udělanou chytřeji, než v některých jiných GC-based jazycích může zdát GC malým problémem, ale to samozřejmě jen čeká na horší situaci, kdy se může projevit.
Druhá část - ona to není poslední verze, ale opět máte pravdu, měl jsem tam dohodit i (prozatím) poslední slušnější řešení. Upravím to ASAP.
[posilam znovu, predchozi prispevek z nejakeho duvodu ceka na schvaleni]
Super, zaprvé moc díky za feedback, který je k věci.
Tu první část jsem myslel trošku jinak, ale asi to vyznělo špatně. Já totiž nezmínil úplný kontext a ten je tento: v dnešní trošku zmatené době se mi ozvali kluci, co sice umí (teoreticky) programovat, protože se to učili ve škole, ale chtějí si kromě nějakého bubble sortu a podobných textových věcí vyzkoušet i nějakou jednoduchou hru. Což je skvělé, že se jen neválí doma (tedy oni se asi doma válí, ale u IDEčka :-).
No a z praxe vím, že je fakt největší problém s udržením stavu aplikace - to je IMHO mnohem důležitější věc na zmínění (při výuce), než důraz na řešení konkrétních algoritmů. Protože ty algoritmy člověk dříve či později nějak odladí, ale to bývá malá a izolovaná věc, kdežto stav se ve větší aplikaci "rozleze" všude a těžko se řídí. S tím GC to asi bylo napsáno nešťastně, protože v kontextu her (a to nemluvíme o áčkových titulech, ale o jednodušších věcech typu tower defense) je to pochopitelně problém, jak sám píšete - ovšem v Go ne tak dobře řešitelný jako v C/C++.
Pozn: ono se "díky" rychlému GC v Go a taky toho, že má alokaci udělanou chytřeji, než v některých jiných GC-based jazycích může zdát GC malým problémem, ale to samozřejmě jen čeká na horší situaci, kdy se může projevit.
Druhá část - ona to není poslední verze, ale opět máte pravdu, měl jsem tam dohodit i (prozatím) poslední slušnější řešení. Upravím to ASAP.
„moc díky za feedback“
Není zač, konečně máte článek, ke kterému dokáži i vygenerovat relevantní zpětnou vazbu :D.
První část (ohledně GC): jasně, proto jsem i zmínil „Předpokládám, že to tak nebylo myšleno [...]“. S tím, co píšete, naprosto souhlasím. Pointa byla opravdu jen o tom, že vyznění mohlo být trochu zavádějící, než byl úmysl.
„Druhá část - ona to není poslední verze, [...]“
To je mi celkem jasné, jinak bych měl víc poznámek - např. o tom, že hra není framerate-independent, ale přijde mi zjevné, že to není záběrem dnešního článku, který by byl už příliš zahlcující (a tipuji si, že se tomu nejspíš bude věnovat budoucí článek).
Opraveno, jak v clanku, tak i v https://github.com/tisnik/go-root/blob/master/article_63/test10.go
Jeste jednou dik za feedback!
> To, že neřešíme životnost krátkodobých objektů -> Musí být naprosto jasné a deterministické, kdy jsou potřeba a kdy už ne.
s timto jednoznacne souhlasim! (ten koment s GC je napsan velmi trefne), ad zkusenosti s vyvojem hry Planet Gula (v jave, coz ma GC), pro zajemce link -- https://www.youtube.com/channel/UCEpamceiLz1OK9yHZQZVU8A
Mezi GC jsou rozdily i mezi alokaci pameti. V Go je to reseno jinak nez v Jave, protoze se nenechali omezit "standardem" datovanym nekdy od dob Pascalu - ze lokalni promenne jsou na zasobnikovem ramci a dynamicky alokovane na halde. V Go je to tak i tak podle analyzy kodu, takze tam v dusledku jsou GC pauzy skutecne male. GC se stop-the-world chovanim to neni - viz blogy o tom, jak a proc GC navrhovali.
Tedy - je GC a GC, nehazme je do jednoho pytle.
Kdybych chtel s detmi s necim zacit, nebude lepsi pouzit treba to zminene Pygame? To je taky nad SDL a s Pythonem je obecne lepsi obeznamenost. Jako je jasny, ze vykonnost je nekde jinde, ale to pro rozhybani panacka a dvou potvor, kterym uhyba, asi nevadi. Nebo nejaky JS framework?
(tedy otazka je, jak decka vubec namotivovat, i kdyz vlastne ted nemaji co delat, tak se jim nechce ... prokleti rychle linky s Yutube a discordem :/)
Pro děti Scratch https://scratch.mit.edu/ nebo Phratch http://jannik-laval.eu/phratch/ či podobné projekty.